@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
@@ -7,7 +7,6 @@ import { ForbiddenError } from '../../errors/errors.js'
7
7
  import { PikkuError, addError } from '../../errors/error-handler.js'
8
8
  import type { PikkuRPC, ResolvedFunction } from './rpc-types.js'
9
9
  import { parseVersionedId } from '../../version.js'
10
- import { encryptJSON } from '../../crypto-utils.js'
11
10
 
12
11
  export class RPCNotFoundError extends PikkuError {
13
12
  public readonly rpcName: string
@@ -118,23 +117,51 @@ export class ContextAwareRPCService {
118
117
  : undefined,
119
118
  }
120
119
 
121
- // Check if it's a namespaced function call (e.g., 'stripe:createCharge')
120
+ // Check addon namespace first (e.g. 'stripe:createCharge')
122
121
  if (funcName.includes(':')) {
123
- return this.invokeAddonFunction<In, Out>(funcName, data, updatedWire)
122
+ try {
123
+ return await this.invokeAddonFunction<In, Out>(
124
+ funcName,
125
+ data,
126
+ updatedWire
127
+ )
128
+ } catch (addonErr) {
129
+ if (!(addonErr instanceof RPCNotFoundError)) throw addonErr
130
+ // Not an addon — fall through to local lookup
131
+ }
124
132
  }
125
133
 
126
- // Main package function
127
- return runPikkuFunc<In, Out>(
128
- 'rpc',
129
- funcName,
130
- getPikkuFunctionName(funcName),
131
- {
132
- auth: this.options.requiresAuth,
133
- singletonServices: this.services,
134
- data: () => data,
135
- wire: updatedWire,
134
+ // Try local function, then fall back to deployment service (remote)
135
+ try {
136
+ return await runPikkuFunc<In, Out>(
137
+ 'rpc',
138
+ funcName,
139
+ getPikkuFunctionName(funcName),
140
+ {
141
+ auth: this.options.requiresAuth,
142
+ singletonServices: this.services,
143
+ data: () => data,
144
+ wire: updatedWire,
145
+ }
146
+ )
147
+ } catch (e) {
148
+ if (e instanceof RPCNotFoundError) {
149
+ // Fall back to deployment service (e.g. CF service binding, Lambda Invoke)
150
+ if (this.services.deploymentService) {
151
+ const session =
152
+ this.wire.getSession && typeof this.wire.getSession === 'function'
153
+ ? await this.wire.getSession()
154
+ : (this.wire as any).session
155
+ return this.services.deploymentService.invoke(
156
+ funcName,
157
+ data,
158
+ session,
159
+ this.wire.traceId
160
+ ) as Promise<Out>
161
+ }
136
162
  }
137
- )
163
+ throw e
164
+ }
138
165
  }
139
166
 
140
167
  /**
@@ -151,10 +178,7 @@ export class ContextAwareRPCService {
151
178
  // Resolve namespace to package name
152
179
  const resolved = resolveNamespace(namespacedFunction)
153
180
  if (!resolved) {
154
- throw new Error(
155
- `Unknown namespace in function reference: ${namespacedFunction}. ` +
156
- `Make sure the package is registered in addons config.`
157
- )
181
+ throw new RPCNotFoundError(namespacedFunction)
158
182
  }
159
183
 
160
184
  // Get the function meta from the addon package
@@ -162,10 +186,7 @@ export class ContextAwareRPCService {
162
186
  const addonFunctionMeta = pikkuState(resolved.package, 'function', 'meta')
163
187
  const funcMeta = addonFunctionMeta[resolved.function]
164
188
  if (!funcMeta) {
165
- throw new Error(
166
- `Function '${resolved.function}' not found in package '${resolved.package}'. ` +
167
- `Available functions: ${Object.keys(addonFunctionMeta).join(', ') || '(none)'}`
168
- )
189
+ throw new RPCNotFoundError(namespacedFunction)
169
190
  }
170
191
  const funcName = funcMeta.pikkuFuncId || resolved.function
171
192
 
@@ -210,17 +231,33 @@ export class ContextAwareRPCService {
210
231
  return this.invokeAddonFunction<In, Out>(rpcName, data, mergedWire)
211
232
  }
212
233
 
213
- return runPikkuFunc<In, Out>(
214
- 'rpc',
215
- rpcName,
216
- getPikkuFunctionName(rpcName),
217
- {
218
- auth: this.options.requiresAuth,
219
- singletonServices: this.services,
220
- data: () => data,
221
- wire: mergedWire,
234
+ try {
235
+ return await runPikkuFunc<In, Out>(
236
+ 'rpc',
237
+ rpcName,
238
+ getPikkuFunctionName(rpcName),
239
+ {
240
+ auth: this.options.requiresAuth,
241
+ singletonServices: this.services,
242
+ data: () => data,
243
+ wire: mergedWire,
244
+ }
245
+ )
246
+ } catch (e) {
247
+ if (e instanceof RPCNotFoundError && this.services.deploymentService) {
248
+ const session =
249
+ this.wire.getSession && typeof this.wire.getSession === 'function'
250
+ ? await this.wire.getSession()
251
+ : (this.wire as any).session
252
+ return this.services.deploymentService.invoke(
253
+ rpcName,
254
+ data,
255
+ session,
256
+ this.wire.traceId
257
+ ) as Promise<Out>
222
258
  }
223
- )
259
+ throw e
260
+ }
224
261
  }
225
262
 
226
263
  public async startWorkflow<In = any>(
@@ -332,94 +369,24 @@ export class ContextAwareRPCService {
332
369
  funcName: string,
333
370
  data: In
334
371
  ): Promise<Out> {
335
- let endpoint: string | undefined
336
- const headers: Record<string, string> = {
337
- 'Content-Type': 'application/json',
338
- }
339
-
340
- const colonIndex = funcName.indexOf(':')
341
- if (colonIndex !== -1) {
342
- const namespace = funcName.substring(0, colonIndex)
343
- const addons = pikkuState(null, 'addons', 'packages')
344
- const pkgConfig = addons.get(namespace)
345
- endpoint = pkgConfig?.rpcEndpoint
346
- }
347
-
348
- if (!endpoint && this.services.deploymentService) {
349
- const deployments =
350
- await this.services.deploymentService.findFunction(funcName)
351
- if (deployments.length > 0) {
352
- endpoint = deployments[0].endpoint
353
- }
354
- }
355
-
356
- if (!endpoint) {
372
+ if (!this.services.deploymentService) {
357
373
  throw new Error(
358
- `No endpoint configured for remote RPC: ${funcName}. ` +
359
- `Configure rpcEndpoint in addons config or set up a DeploymentService.`
360
- )
361
- }
362
-
363
- let secret: string | undefined
364
- if (this.services.secrets) {
365
- try {
366
- secret = await this.services.secrets.getSecret('PIKKU_REMOTE_SECRET')
367
- } catch {}
368
- }
369
- if (secret) {
370
- if (!this.services.jwt) {
371
- throw new Error(
372
- 'PIKKU_REMOTE_SECRET is set but JWT service is not available'
373
- )
374
- }
375
- const session =
376
- this.wire.getSession && typeof this.wire.getSession === 'function'
377
- ? await this.wire.getSession()
378
- : (this.wire as any).session
379
- const sessionEnc = session
380
- ? await encryptJSON(secret, { session })
381
- : undefined
382
- const token = await this.services.jwt.encode(
383
- { value: 5, unit: 'minute' },
384
- {
385
- aud: 'pikku-remote',
386
- fn: funcName,
387
- iat: Date.now(),
388
- session: sessionEnc,
389
- }
374
+ `No DeploymentService configured for remote RPC: ${funcName}. ` +
375
+ `Set up a DeploymentService to enable remote function calls.`
390
376
  )
391
- headers.Authorization = `Bearer ${token}`
392
377
  }
393
378
 
394
- const url = `${endpoint}/rpc/${encodeURIComponent(funcName)}`
395
- const timeoutMs = 30_000
396
- const response = await fetch(url, {
397
- method: 'POST',
398
- headers,
399
- body: JSON.stringify({ data }),
400
- signal: AbortSignal.timeout(timeoutMs),
401
- })
402
-
403
- if (!response.ok) {
404
- let errorBody: string
405
- try {
406
- errorBody = JSON.stringify(await response.json())
407
- } catch {
408
- errorBody = await response.text()
409
- }
410
- throw new Error(
411
- `Remote RPC call failed: ${response.status} ${response.statusText}. ${errorBody}`
412
- )
413
- }
379
+ const session =
380
+ this.wire.getSession && typeof this.wire.getSession === 'function'
381
+ ? await this.wire.getSession()
382
+ : (this.wire as any).session
414
383
 
415
- try {
416
- return (await response.json()) as Out
417
- } catch {
418
- const text = await response.text()
419
- throw new Error(
420
- `Remote RPC call returned non-JSON response: ${response.status} ${response.statusText}. ${text}`
421
- )
422
- }
384
+ return this.services.deploymentService.invoke(
385
+ funcName,
386
+ data,
387
+ session,
388
+ this.wire.traceId
389
+ ) as Promise<Out>
423
390
  }
424
391
  }
425
392
 
@@ -50,7 +50,7 @@ describe('wireScheduler', () => {
50
50
  assert.equal(tasks.get('test-task'), mockTask)
51
51
  })
52
52
 
53
- test('should throw error when task metadata not found', () => {
53
+ test('should skip when scheduled task metadata not found', () => {
54
54
  const mockTask: CoreScheduledTask = {
55
55
  name: 'missing-meta-task',
56
56
  schedule: '0 0 * * *',
@@ -60,16 +60,10 @@ describe('wireScheduler', () => {
60
60
  },
61
61
  }
62
62
 
63
- assert.throws(
64
- () => wireScheduler(mockTask),
65
- (error: any) => {
66
- assert.equal(
67
- error.message,
68
- "Missing generated metadata for scheduled task 'missing-meta-task'"
69
- )
70
- return true
71
- }
72
- )
63
+ wireScheduler(mockTask)
64
+
65
+ const tasks = pikkuState(null, 'scheduler', 'tasks')
66
+ assert.equal(tasks.has('missing-meta-task'), false)
73
67
  })
74
68
 
75
69
  test('should throw error when scheduled task already exists', () => {
@@ -1,7 +1,6 @@
1
1
  import type { PikkuWire, CoreUserSession } from '../../types/core.types.js'
2
2
  import type { CoreScheduledTask } from './scheduler.types.js'
3
3
  import { getErrorResponse, PikkuError } from '../../errors/error-handler.js'
4
- import { PikkuMissingMetaError } from '../../errors/errors.js'
5
4
  import {
6
5
  getSingletonServices,
7
6
  getCreateWireServices,
@@ -20,6 +19,8 @@ import type {
20
19
  export type RunScheduledTasksParams = {
21
20
  name: string
22
21
  session?: CoreUserSession
22
+ /** Pre-resolved trace ID */
23
+ traceId?: string
23
24
  }
24
25
 
25
26
  export const wireScheduler = <
@@ -32,9 +33,10 @@ export const wireScheduler = <
32
33
  const meta = pikkuState(null, 'scheduler', 'meta')
33
34
  const taskMeta = meta[scheduledTask.name]
34
35
  if (!taskMeta) {
35
- throw new PikkuMissingMetaError(
36
- `Missing generated metadata for scheduled task '${scheduledTask.name}'`
36
+ console.warn(
37
+ `[pikku] Skipping scheduled task '${scheduledTask.name}' — metadata not found. Consider moving this wiring to its own file.`
37
38
  )
39
+ return
38
40
  }
39
41
  addFunction(taskMeta.pikkuFuncId, {
40
42
  func: scheduledTask.func.func,
@@ -69,9 +71,14 @@ class ScheduledTaskSkippedError extends PikkuError {
69
71
  export async function runScheduledTask({
70
72
  name,
71
73
  session,
74
+ traceId,
72
75
  }: RunScheduledTasksParams): Promise<void> {
73
76
  const singletonServices = getSingletonServices()
74
77
  const createWireServices = getCreateWireServices()
78
+ const resolvedTraceId = traceId ?? `cron-${name}-${Date.now()}`
79
+ const scopedLogger =
80
+ singletonServices.logger.scope?.(resolvedTraceId) ??
81
+ singletonServices.logger
75
82
  const task = pikkuState(null, 'scheduler', 'tasks').get(name)
76
83
  const meta = pikkuState(null, 'scheduler', 'meta')[name]
77
84
 
@@ -91,6 +98,7 @@ export async function runScheduledTask({
91
98
 
92
99
  // Create the scheduled task wire object
93
100
  const wire: PikkuWire = {
101
+ traceId: resolvedTraceId,
94
102
  scheduledTask: {
95
103
  name,
96
104
  schedule: task.schedule,
@@ -103,7 +111,7 @@ export async function runScheduledTask({
103
111
  }
104
112
 
105
113
  try {
106
- singletonServices.logger.info(
114
+ scopedLogger.info(
107
115
  `Running schedule task: ${name} | schedule: ${task.schedule}`
108
116
  )
109
117
 
@@ -122,7 +130,7 @@ export async function runScheduledTask({
122
130
  } catch (e: any) {
123
131
  const errorResponse = getErrorResponse(e)
124
132
  if (errorResponse != null) {
125
- singletonServices.logger.error(e)
133
+ scopedLogger.error(e)
126
134
  }
127
135
  throw e
128
136
  }
@@ -7,7 +7,6 @@ import {
7
7
  getTriggerMeta,
8
8
  } from './trigger-runner.js'
9
9
  import { resetPikkuState, pikkuState } from '../../pikku-state.js'
10
- import { PikkuMissingMetaError } from '../../errors/errors.js'
11
10
 
12
11
  beforeEach(() => {
13
12
  resetPikkuState()
@@ -24,11 +23,11 @@ describe('wireTrigger', () => {
24
23
  assert.ok(triggers.has('myTrigger'))
25
24
  })
26
25
 
27
- test('should throw PikkuMissingMetaError when meta is missing', () => {
28
- assert.throws(
29
- () => wireTrigger({ name: 'noMeta', func: async () => {} } as any),
30
- (err: any) => err instanceof PikkuMissingMetaError
31
- )
26
+ test('should skip when trigger metadata not found', () => {
27
+ wireTrigger({ name: 'noMeta', func: async () => {} } as any)
28
+
29
+ const triggers = getRegisteredTriggers()
30
+ assert.ok(!triggers.has('noMeta'))
32
31
  })
33
32
  })
34
33
 
@@ -43,11 +42,11 @@ describe('wireTriggerSource', () => {
43
42
  assert.ok(sources.has('mySource'))
44
43
  })
45
44
 
46
- test('should throw PikkuMissingMetaError when source meta is missing', () => {
47
- assert.throws(
48
- () => wireTriggerSource({ name: 'noMeta', func: async () => {} } as any),
49
- (err: any) => err instanceof PikkuMissingMetaError
50
- )
45
+ test('should skip when trigger source metadata not found', () => {
46
+ wireTriggerSource({ name: 'noMeta', func: async () => {} } as any)
47
+
48
+ const sources = pikkuState(null, 'trigger', 'triggerSources')
49
+ assert.ok(!sources.has('noMeta'))
51
50
  })
52
51
 
53
52
  test('should throw when trigger source already exists', () => {
@@ -21,9 +21,10 @@ export const wireTrigger = (trigger: CoreTrigger) => {
21
21
  const meta = pikkuState(null, 'trigger', 'meta')
22
22
  const triggerMeta = meta[trigger.name]
23
23
  if (!triggerMeta) {
24
- throw new PikkuMissingMetaError(
25
- `Missing generated metadata for trigger '${trigger.name}'`
24
+ console.warn(
25
+ `[pikku] Skipping trigger '${trigger.name}' — metadata not found. Consider moving this wiring to its own file.`
26
26
  )
27
+ return
27
28
  }
28
29
 
29
30
  addFunction(triggerMeta.pikkuFuncId, trigger.func as any)
@@ -43,9 +44,10 @@ export const wireTriggerSource = <TInput = unknown, TOutput = unknown>(
43
44
  const sourceMeta = pikkuState(null, 'trigger', 'sourceMeta')
44
45
  const triggerSourceMeta = sourceMeta[source.name]
45
46
  if (!triggerSourceMeta) {
46
- throw new PikkuMissingMetaError(
47
- `Missing generated metadata for trigger source '${source.name}'`
47
+ console.warn(
48
+ `[pikku] Skipping trigger source '${source.name}' — metadata not found. Consider moving this wiring to its own file.`
48
49
  )
50
+ return
49
51
  }
50
52
 
51
53
  const triggerSources = pikkuState(null, 'trigger', 'triggerSources')
@@ -1,28 +1,29 @@
1
1
  import { pikkuState } from '../../../pikku-state.js'
2
2
  import { addFunction } from '../../../function/function-runner.js'
3
- import { PikkuMissingMetaError } from '../../../errors/errors.js'
4
3
 
5
4
  /**
6
5
  * Add a workflow to the system
7
6
  * This is called by the generated workflow wirings
8
7
  */
9
- export const addWorkflow = (workflowName: string, workflowFunc: any) => {
10
- // Get workflow metadata from inspector
11
- const meta = pikkuState(null, 'workflows', 'meta')
8
+ export const addWorkflow = (
9
+ workflowName: string,
10
+ workflowFunc: any,
11
+ packageName: string | null = null
12
+ ) => {
13
+ const meta = pikkuState(packageName, 'workflows', 'meta')
12
14
  const workflowMeta = meta[workflowName]
13
15
  if (!workflowMeta) {
14
- throw new PikkuMissingMetaError(
15
- `Missing generated metadata for workflow '${workflowName}'`
16
+ console.warn(
17
+ `[pikku] Skipping workflow '${workflowName}' — metadata not found. Consider moving this wiring to its own file.`
16
18
  )
19
+ return
17
20
  }
18
21
 
19
- // Store workflow definition in state
20
- const registrations = pikkuState(null, 'workflows', 'registrations')
22
+ const registrations = pikkuState(packageName, 'workflows', 'registrations')
21
23
  registrations.set(workflowName, {
22
24
  name: workflowName,
23
25
  func: workflowFunc,
24
26
  })
25
27
 
26
- // Register the function with pikku
27
- addFunction(workflowMeta.pikkuFuncId, workflowFunc)
28
+ addFunction(workflowMeta.pikkuFuncId, workflowFunc, packageName)
28
29
  }
@@ -116,8 +116,22 @@ function isTemplate(value: unknown): value is TemplateValue {
116
116
  }
117
117
 
118
118
  function getWorkflowMeta(name: string): WorkflowRuntimeMeta | undefined {
119
- const meta = pikkuState(null, 'workflows', 'meta')
120
- return meta[name]
119
+ const rootMeta = pikkuState(null, 'workflows', 'meta')
120
+ if (rootMeta[name]) return rootMeta[name]
121
+
122
+ const colonIndex = name.indexOf(':')
123
+ if (colonIndex !== -1) {
124
+ const namespace = name.substring(0, colonIndex)
125
+ const localName = name.substring(colonIndex + 1)
126
+ const addons = pikkuState(null, 'addons', 'packages')
127
+ const pkgConfig = addons?.get(namespace)
128
+ if (pkgConfig) {
129
+ const addonMeta = pikkuState(pkgConfig.package, 'workflows', 'meta')
130
+ if (addonMeta?.[localName]) return addonMeta[localName]
131
+ }
132
+ }
133
+
134
+ return undefined
121
135
  }
122
136
 
123
137
  function resolveNextFromConfig(next: unknown, branchKey?: string): string[] {
@@ -798,9 +812,10 @@ export async function runWorkflowGraph(
798
812
  rpcService?: any,
799
813
  inline?: boolean,
800
814
  startNode?: string,
801
- wire?: WorkflowRunWire
815
+ wire?: WorkflowRunWire,
816
+ overrideMeta?: WorkflowRuntimeMeta
802
817
  ): Promise<{ runId: string }> {
803
- const meta = getWorkflowMeta(graphName)
818
+ const meta = overrideMeta ?? getWorkflowMeta(graphName)
804
819
  if (!meta?.nodes) {
805
820
  throw new Error(`Workflow graph '${graphName}' not found`)
806
821
  }
@@ -20,13 +20,23 @@ export {
20
20
  type PikkuWorkflowGraphResult,
21
21
  } from './graph/wire-workflow-graph.js'
22
22
 
23
- // Workflow helpers (runtime factories for HTTP handlers)
23
+ // Graph validation and dynamic workflow utilities
24
24
  export {
25
- workflow,
26
- workflowStart,
27
- workflowStatus,
28
- graphStart,
29
- } from './workflow-helpers.js'
25
+ validateWorkflowWiring,
26
+ computeEntryNodeIds,
27
+ } from './graph/graph-validation.js'
28
+
29
+ // Queue worker functions (registered by codegen, executed at runtime)
30
+ export {
31
+ pikkuWorkflowWorkerFunc,
32
+ pikkuWorkflowOrchestratorFunc,
33
+ pikkuWorkflowSleeperFunc,
34
+ } from './workflow-queue-workers.js'
35
+ export type {
36
+ WorkflowStepInput as WorkflowStepQueueInput,
37
+ PikkuWorkflowOrchestratorInput,
38
+ PikkuWorkflowSleeperInput,
39
+ } from './workflow-queue-workers.js'
30
40
 
31
41
  // Re-export all types from workflow.types
32
42
  export type {
@@ -37,6 +47,7 @@ export type {
37
47
  WorkflowVersionStatus,
38
48
  StepStatus,
39
49
  WorkflowRun,
50
+ WorkflowRunStatus,
40
51
  StepState,
41
52
  WorkflowRunService,
42
53
  CoreWorkflow,