@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,6 +1,33 @@
1
- import { runPikkuFunc } from '../../function/function-runner.js';
1
+ import { runPikkuFunc, addFunction } from '../../function/function-runner.js';
2
+ import { pikkuWorkflowWorkerFunc, pikkuWorkflowOrchestratorFunc, pikkuWorkflowSleeperFunc, } from './workflow-queue-workers.js';
3
+ import { wireQueueWorker } from '../queue/queue-runner.js';
2
4
  import { getSingletonServices, getCreateWireServices, pikkuState, } from '../../pikku-state.js';
3
5
  import { getDurationInMilliseconds } from '../../time-utils.js';
6
+ const resolveWorkflowMeta = (name) => {
7
+ const rootMeta = pikkuState(null, 'workflows', 'meta');
8
+ if (rootMeta[name]) {
9
+ return { meta: rootMeta[name], packageName: null, resolvedName: name };
10
+ }
11
+ const colonIndex = name.indexOf(':');
12
+ if (colonIndex !== -1) {
13
+ const namespace = name.substring(0, colonIndex);
14
+ const localName = name.substring(colonIndex + 1);
15
+ const addons = pikkuState(null, 'addons', 'packages');
16
+ const pkgConfig = addons?.get(namespace);
17
+ if (pkgConfig) {
18
+ const addonMeta = pikkuState(pkgConfig.package, 'workflows', 'meta');
19
+ if (addonMeta?.[localName]) {
20
+ return {
21
+ meta: addonMeta[localName],
22
+ packageName: pkgConfig.package,
23
+ resolvedName: localName,
24
+ };
25
+ }
26
+ }
27
+ }
28
+ return null;
29
+ };
30
+ const toKebab = (s) => s.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
4
31
  import { continueGraph, executeGraphStep, runWorkflowGraph, runFromMeta, } from './graph/graph-runner.js';
5
32
  import { PikkuError, addError } from '../../errors/error-handler.js';
6
33
  import { RPCNotFoundError } from '../rpc/rpc-runner.js';
@@ -88,7 +115,62 @@ export class PikkuWorkflowService {
88
115
  get logger() {
89
116
  return getSingletonServices()?.logger;
90
117
  }
91
- constructor() { }
118
+ constructor() {
119
+ const functions = pikkuState(null, 'function', 'functions');
120
+ const functionsMeta = pikkuState(null, 'function', 'meta');
121
+ // Minimal meta for internal workflow functions (satisfies FunctionMeta)
122
+ const mkMeta = (funcId) => ({
123
+ pikkuFuncId: funcId,
124
+ sessionless: true,
125
+ functionType: 'helper',
126
+ inputSchemaName: null,
127
+ outputSchemaName: null,
128
+ });
129
+ const queueMeta = pikkuState(null, 'queue', 'meta');
130
+ const registerWorkflowFunc = (funcId, func, queueName) => {
131
+ if (functions.has(funcId))
132
+ return;
133
+ addFunction(funcId, func);
134
+ if (!queueMeta[queueName]) {
135
+ queueMeta[queueName] = { pikkuFuncId: funcId, name: queueName };
136
+ }
137
+ wireQueueWorker({ name: queueName, func });
138
+ if (!functionsMeta[funcId]) {
139
+ functionsMeta[funcId] = mkMeta(funcId);
140
+ }
141
+ };
142
+ // Register shared queue workers for monolith deployments
143
+ registerWorkflowFunc('pikkuWorkflowOrchestrator', { func: pikkuWorkflowOrchestratorFunc }, 'pikku-workflow-orchestrator');
144
+ registerWorkflowFunc('pikkuWorkflowStepWorker', { func: pikkuWorkflowWorkerFunc }, 'pikku-workflow-step-worker');
145
+ // Register per-workflow queue workers (root + addon packages)
146
+ const registerQueueWorkers = (queueMeta) => {
147
+ for (const [queueName, meta] of Object.entries(queueMeta)) {
148
+ if (functions.has(meta.pikkuFuncId))
149
+ continue;
150
+ if (queueName.startsWith('wf-orchestrator-')) {
151
+ registerWorkflowFunc(meta.pikkuFuncId, { func: pikkuWorkflowOrchestratorFunc }, queueName);
152
+ }
153
+ else if (queueName.startsWith('wf-step-')) {
154
+ registerWorkflowFunc(meta.pikkuFuncId, { func: pikkuWorkflowWorkerFunc }, queueName);
155
+ }
156
+ }
157
+ };
158
+ registerQueueWorkers(pikkuState(null, 'queue', 'meta'));
159
+ const addons = pikkuState(null, 'addons', 'packages');
160
+ if (addons) {
161
+ for (const [, addon] of addons) {
162
+ const addonQueueMeta = pikkuState(addon.package, 'queue', 'meta');
163
+ if (addonQueueMeta) {
164
+ registerQueueWorkers(addonQueueMeta);
165
+ }
166
+ }
167
+ }
168
+ if (!functions.has('pikkuWorkflowSleeper')) {
169
+ addFunction('pikkuWorkflowSleeper', {
170
+ func: pikkuWorkflowSleeperFunc,
171
+ });
172
+ }
173
+ }
92
174
  /**
93
175
  * Check if a run is executing inline (without queues)
94
176
  */
@@ -115,6 +197,47 @@ export class PikkuWorkflowService {
115
197
  await this.upsertWorkflowVersion(name, meta.graphHash, meta, meta.source);
116
198
  }
117
199
  }
200
+ /**
201
+ * Get minimal workflow run status with step summaries.
202
+ * Used by the public API — the console addon provides the full verbose view.
203
+ */
204
+ async getRunStatus(id) {
205
+ const run = await this.getRun(id);
206
+ if (!run)
207
+ return null;
208
+ const history = await this.getRunHistory(id);
209
+ const terminalStatuses = new Set(['completed', 'failed', 'cancelled']);
210
+ // Build step summaries from history (latest attempt per step)
211
+ const stepMap = new Map();
212
+ for (const step of history) {
213
+ const existing = stepMap.get(step.stepName);
214
+ if (!existing || step.updatedAt > existing.completedAt) {
215
+ stepMap.set(step.stepName, {
216
+ status: step.status,
217
+ startedAt: step.runningAt ?? step.createdAt,
218
+ completedAt: step.succeededAt ?? step.failedAt,
219
+ });
220
+ }
221
+ }
222
+ const steps = [...stepMap.entries()].map(([name, s]) => ({
223
+ name,
224
+ status: s.status,
225
+ duration: s.startedAt && s.completedAt
226
+ ? s.completedAt.getTime() - s.startedAt.getTime()
227
+ : undefined,
228
+ }));
229
+ return {
230
+ id: run.id,
231
+ status: run.status,
232
+ startedAt: run.createdAt,
233
+ completedAt: terminalStatuses.has(run.status) ? run.updatedAt : undefined,
234
+ steps,
235
+ output: run.status === 'completed' ? run.output : undefined,
236
+ error: run.error
237
+ ? { message: run.error.message ?? 'Unknown error' }
238
+ : undefined,
239
+ };
240
+ }
118
241
  // ============================================================================
119
242
  // Workflow Lifecycle Methods
120
243
  // ============================================================================
@@ -122,13 +245,19 @@ export class PikkuWorkflowService {
122
245
  * Resume a paused workflow by triggering the orchestrator
123
246
  * @param runId - Run ID
124
247
  */
125
- async resumeWorkflow(runId) {
248
+ async resumeWorkflow(runId, workflowName) {
126
249
  const queueService = this.verifyQueueService();
127
- await queueService.add(this.getConfig().orchestratorQueueName, { runId });
250
+ if (!workflowName) {
251
+ const run = await this.getRun(runId);
252
+ workflowName = run?.workflow;
253
+ }
254
+ await queueService.add(this.getOrchestratorQueueName(workflowName), {
255
+ runId,
256
+ });
128
257
  }
129
258
  async queueStepWorker(runId, stepName, rpcName, data) {
130
259
  const queueService = this.verifyQueueService();
131
- await queueService.add(this.getConfig().stepWorkerQueueName, JSON.parse(JSON.stringify({
260
+ await queueService.add(this.getStepWorkerQueueName(rpcName), JSON.parse(JSON.stringify({
132
261
  runId,
133
262
  stepName,
134
263
  rpcName,
@@ -149,9 +278,13 @@ export class PikkuWorkflowService {
149
278
  * @param runId - Run ID
150
279
  * @param retryDelay - Delay in milliseconds or duration string (optional)
151
280
  */
152
- async scheduleOrchestratorRetry(runId, retryDelay) {
281
+ async scheduleOrchestratorRetry(runId, retryDelay, workflowName) {
153
282
  const queueService = this.verifyQueueService();
154
- await queueService.add(this.getConfig().orchestratorQueueName, { runId }, retryDelay ? { delay: getDurationInMilliseconds(retryDelay) } : undefined);
283
+ if (!workflowName) {
284
+ const run = await this.getRun(runId);
285
+ workflowName = run?.workflow;
286
+ }
287
+ await queueService.add(this.getOrchestratorQueueName(workflowName), { runId }, retryDelay ? { delay: getDurationInMilliseconds(retryDelay) } : undefined);
155
288
  }
156
289
  /**
157
290
  * Start a new workflow run
@@ -160,21 +293,30 @@ export class PikkuWorkflowService {
160
293
  * @param options.startNode - Starting node ID for graph workflows (from wire config)
161
294
  */
162
295
  async startWorkflow(name, input, wire, rpcService, options) {
163
- // Check meta to determine workflow type
164
- const meta = pikkuState(null, 'workflows', 'meta');
165
- const workflowMeta = meta[name];
296
+ // Resolve workflow from static meta (root or addon namespace), then dynamic DB
297
+ const resolved = resolveWorkflowMeta(name);
298
+ let workflowMeta = resolved?.meta;
299
+ const packageName = resolved?.packageName ?? null;
300
+ if (!workflowMeta) {
301
+ const dynamicWorkflows = await this.getAIGeneratedWorkflows();
302
+ const match = dynamicWorkflows.find((w) => w.workflowName === name);
303
+ if (match?.graph) {
304
+ workflowMeta = match.graph;
305
+ }
306
+ }
166
307
  if (!workflowMeta) {
167
308
  throw new WorkflowNotFoundError(name);
168
309
  }
169
- if (workflowMeta.source === 'graph' || workflowMeta.source === 'ai-agent') {
310
+ if (workflowMeta.source === 'graph' ||
311
+ workflowMeta.source === 'dynamic-workflow') {
170
312
  const shouldInline = options?.inline ||
171
313
  workflowMeta.inline ||
172
314
  !getSingletonServices()?.queueService;
173
- return runWorkflowGraph(this, name, input, rpcService, shouldInline, options?.startNode, wire);
315
+ return runWorkflowGraph(this, name, input, rpcService, shouldInline, options?.startNode, wire, workflowMeta);
174
316
  }
175
317
  // DSL workflow - check registration exists
176
- const registrations = pikkuState(null, 'workflows', 'registrations');
177
- const workflow = registrations.get(name);
318
+ const registrations = pikkuState(packageName, 'workflows', 'registrations');
319
+ const workflow = registrations.get(resolved?.resolvedName ?? name);
178
320
  if (!workflow) {
179
321
  throw new WorkflowNotFoundError(name);
180
322
  }
@@ -231,15 +373,17 @@ export class PikkuWorkflowService {
231
373
  if (!run) {
232
374
  throw new WorkflowRunNotFoundError(runId);
233
375
  }
234
- const meta = pikkuState(null, 'workflows', 'meta');
235
- const workflowMeta = meta[run.workflow];
376
+ const resolved = resolveWorkflowMeta(run.workflow);
377
+ const workflowMeta = resolved?.meta;
378
+ const pkgName = resolved?.packageName ?? null;
236
379
  if (run.graphHash &&
237
380
  workflowMeta?.graphHash &&
238
381
  run.graphHash !== workflowMeta.graphHash) {
239
382
  await this.runVersionMismatchFallback(run, workflowMeta, rpcService);
240
383
  return;
241
384
  }
242
- if (workflowMeta?.source === 'graph') {
385
+ if (workflowMeta?.source === 'graph' ||
386
+ workflowMeta?.source === 'dynamic-workflow') {
243
387
  await continueGraph(this, runId, run.workflow);
244
388
  const updatedRun = await this.getRun(runId);
245
389
  if (updatedRun?.status === 'completed') {
@@ -251,13 +395,32 @@ export class PikkuWorkflowService {
251
395
  }
252
396
  return;
253
397
  }
254
- const registrations = pikkuState(null, 'workflows', 'registrations');
255
- const workflow = registrations.get(run.workflow);
398
+ if (!workflowMeta) {
399
+ const dynamicWorkflows = await this.getAIGeneratedWorkflows();
400
+ const match = dynamicWorkflows.find((w) => w.workflowName === run.workflow);
401
+ if (match?.graph) {
402
+ await continueGraph(this, runId, run.workflow, match.graph);
403
+ const updatedRun = await this.getRun(runId);
404
+ if (updatedRun?.status === 'completed') {
405
+ await this.onChildWorkflowCompleted(updatedRun, updatedRun.output);
406
+ }
407
+ else if (updatedRun?.status === 'failed' ||
408
+ updatedRun?.status === 'cancelled') {
409
+ await this.onChildWorkflowFailed(updatedRun, new Error(updatedRun.error?.message || 'Child workflow failed'));
410
+ }
411
+ return;
412
+ }
413
+ }
414
+ const registrations = pikkuState(pkgName, 'workflows', 'registrations');
415
+ const workflow = registrations.get(resolved?.resolvedName ?? run.workflow);
256
416
  if (!workflow) {
257
417
  throw new WorkflowNotFoundError(run.workflow);
258
418
  }
259
419
  await this.withRunLock(runId, async () => {
260
- const workflowWire = this.createWorkflowWire(run.workflow, runId, rpcService);
420
+ const addonNs = run.workflow.includes(':')
421
+ ? run.workflow.substring(0, run.workflow.indexOf(':'))
422
+ : null;
423
+ const workflowWire = this.createWorkflowWire(run.workflow, runId, rpcService, addonNs);
261
424
  workflowWire.pikkuUserId = run.wire?.pikkuUserId;
262
425
  const wire = {
263
426
  workflow: workflowWire,
@@ -271,6 +434,7 @@ export class PikkuWorkflowService {
271
434
  wire,
272
435
  createWireServices: getCreateWireServices(),
273
436
  data: () => run.input,
437
+ packageName: pkgName ?? undefined,
274
438
  });
275
439
  await this.updateRunStatus(runId, 'completed', result);
276
440
  await this.onChildWorkflowCompleted(run, result);
@@ -376,7 +540,7 @@ export class PikkuWorkflowService {
376
540
  const meta = pikkuState(null, 'workflows', 'meta');
377
541
  const workflowMeta = meta[run.workflow];
378
542
  const isGraphWorkflow = workflowMeta?.source === 'graph' ||
379
- workflowMeta?.source === 'ai-agent';
543
+ workflowMeta?.source === 'dynamic-workflow';
380
544
  if (isGraphWorkflow &&
381
545
  workflowMeta?.nodes &&
382
546
  stepName in workflowMeta.nodes) {
@@ -514,7 +678,7 @@ export class PikkuWorkflowService {
514
678
  // Map step retry options to queue job options
515
679
  const retries = stepOptions?.retries ?? 0;
516
680
  const retryDelay = stepOptions?.retryDelay;
517
- await getSingletonServices().queueService.add(this.getConfig().stepWorkerQueueName, JSON.parse(JSON.stringify({
681
+ await getSingletonServices().queueService.add(this.getStepWorkerQueueName(rpcName), JSON.parse(JSON.stringify({
518
682
  runId,
519
683
  stepName,
520
684
  rpcName,
@@ -758,7 +922,7 @@ export class PikkuWorkflowService {
758
922
  throw new WorkflowSuspendedException(runId, reason);
759
923
  });
760
924
  }
761
- createWorkflowWire(name, runId, rpcService) {
925
+ createWorkflowWire(name, runId, rpcService, addonNamespace) {
762
926
  const workflowWire = {
763
927
  name,
764
928
  runId,
@@ -767,7 +931,10 @@ export class PikkuWorkflowService {
767
931
  do: async (stepName, rpcNameOrFn, dataOrOptions, options) => {
768
932
  this.verifyStepName(stepName);
769
933
  if (typeof rpcNameOrFn === 'string') {
770
- return await this.rpcStep(runId, stepName, rpcNameOrFn, dataOrOptions, rpcService, options);
934
+ const resolvedRpcName = addonNamespace && !rpcNameOrFn.includes(':')
935
+ ? `${addonNamespace}:${rpcNameOrFn}`
936
+ : rpcNameOrFn;
937
+ return await this.rpcStep(runId, stepName, resolvedRpcName, dataOrOptions, rpcService, options);
771
938
  }
772
939
  else {
773
940
  return await this.inlineStep(runId, stepName, rpcNameOrFn, dataOrOptions);
@@ -800,4 +967,29 @@ export class PikkuWorkflowService {
800
967
  sleeperRPCName: workflow?.sleeperRPCName ?? 'pikkuWorkflowSleeper',
801
968
  };
802
969
  }
970
+ /**
971
+ * Get the orchestrator queue name for a specific workflow.
972
+ * Checks queue meta for a per-workflow queue first (e.g. wf-orchestrator-{name}),
973
+ * falls back to the shared orchestrator queue.
974
+ */
975
+ getOrchestratorQueueName(workflowName) {
976
+ if (workflowName) {
977
+ const perWorkflow = `wf-orchestrator-${toKebab(workflowName)}`;
978
+ const registrations = pikkuState(null, 'queue', 'registrations');
979
+ if (registrations.has(perWorkflow)) {
980
+ return perWorkflow;
981
+ }
982
+ }
983
+ return this.getConfig().orchestratorQueueName;
984
+ }
985
+ getStepWorkerQueueName(rpcName) {
986
+ if (rpcName) {
987
+ const perStep = `wf-step-${toKebab(rpcName)}`;
988
+ const registrations = pikkuState(null, 'queue', 'registrations');
989
+ if (registrations.has(perStep)) {
990
+ return perStep;
991
+ }
992
+ }
993
+ return this.getConfig().stepWorkerQueueName;
994
+ }
803
995
  }
@@ -0,0 +1,40 @@
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
+ import type { PikkuRPC } from '../rpc/rpc-types.js';
9
+ export interface WorkflowStepInput {
10
+ runId: string;
11
+ stepName: string;
12
+ rpcName: string;
13
+ data: unknown;
14
+ }
15
+ export interface PikkuWorkflowOrchestratorInput {
16
+ runId: string;
17
+ }
18
+ export interface PikkuWorkflowSleeperInput {
19
+ runId: string;
20
+ stepId: string;
21
+ }
22
+ /**
23
+ * Step worker — executes individual workflow steps dispatched via queue.
24
+ * The orchestrator queues steps here when they're async (not inline).
25
+ */
26
+ export declare function pikkuWorkflowWorkerFunc(_services: Record<string, unknown>, { runId, stepName, rpcName, data }: WorkflowStepInput, { rpc }: {
27
+ rpc: PikkuRPC;
28
+ }): Promise<void>;
29
+ /**
30
+ * Orchestrator — resumes workflow execution after an async step completes.
31
+ * Called when a step worker finishes and the workflow needs to continue.
32
+ */
33
+ export declare function pikkuWorkflowOrchestratorFunc(_services: Record<string, unknown>, { runId }: PikkuWorkflowOrchestratorInput, { rpc }: {
34
+ rpc: PikkuRPC;
35
+ }): Promise<void>;
36
+ /**
37
+ * Sleeper — wakes a workflow after a workflow.sleep() duration expires.
38
+ * Triggered by a delayed queue message or scheduler callback.
39
+ */
40
+ export declare function pikkuWorkflowSleeperFunc(_services: Record<string, unknown>, { runId, stepId }: PikkuWorkflowSleeperInput): Promise<void>;
@@ -0,0 +1,41 @@
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
+ import { getSingletonServices } from '../../pikku-state.js';
9
+ /**
10
+ * Step worker — executes individual workflow steps dispatched via queue.
11
+ * The orchestrator queues steps here when they're async (not inline).
12
+ */
13
+ export async function pikkuWorkflowWorkerFunc(_services, { runId, stepName, rpcName, data }, { rpc }) {
14
+ const services = getSingletonServices();
15
+ if (!services?.workflowService) {
16
+ throw new Error(`Workflow service not initialized: cannot execute workflow step for runId ${runId}, stepName ${stepName}`);
17
+ }
18
+ await services.workflowService.executeWorkflowStep(runId, stepName, rpcName, data, rpc);
19
+ }
20
+ /**
21
+ * Orchestrator — resumes workflow execution after an async step completes.
22
+ * Called when a step worker finishes and the workflow needs to continue.
23
+ */
24
+ export async function pikkuWorkflowOrchestratorFunc(_services, { runId }, { rpc }) {
25
+ const services = getSingletonServices();
26
+ if (!services?.workflowService) {
27
+ throw new Error(`Workflow service not initialized: cannot orchestrate workflow for runId ${runId}`);
28
+ }
29
+ await services.workflowService.orchestrateWorkflow(runId, rpc);
30
+ }
31
+ /**
32
+ * Sleeper — wakes a workflow after a workflow.sleep() duration expires.
33
+ * Triggered by a delayed queue message or scheduler callback.
34
+ */
35
+ export async function pikkuWorkflowSleeperFunc(_services, { runId, stepId }) {
36
+ const services = getSingletonServices();
37
+ if (!services?.workflowService) {
38
+ throw new Error(`Workflow service not initialized: cannot execute workflow sleep completed for runId ${runId}, stepId ${stepId}`);
39
+ }
40
+ await services.workflowService.executeWorkflowSleepCompleted(runId, stepId);
41
+ }
@@ -90,6 +90,21 @@ export interface StepState {
90
90
  /** Timestamp when step failed */
91
91
  failedAt?: Date;
92
92
  }
93
+ export interface WorkflowRunStatus {
94
+ id: string;
95
+ status: WorkflowStatus;
96
+ startedAt: Date;
97
+ completedAt?: Date;
98
+ steps: Array<{
99
+ name: string;
100
+ status: StepStatus;
101
+ duration?: number;
102
+ }>;
103
+ output?: unknown;
104
+ error?: {
105
+ message: string;
106
+ };
107
+ }
93
108
  export interface WorkflowRunService {
94
109
  listRuns(options?: {
95
110
  workflowName?: string;
@@ -170,6 +185,7 @@ export type WorkflowsMeta = Record<string, CommonWireMeta & {
170
185
  context?: WorkflowContext;
171
186
  dsl?: boolean;
172
187
  inline?: boolean;
188
+ expose?: boolean;
173
189
  }>;
174
190
  /**
175
191
  * Unified workflow runtime meta (used by runtime to execute workflows)
@@ -182,7 +198,7 @@ export interface WorkflowRuntimeMeta {
182
198
  /** Pikku function name (for execution) */
183
199
  pikkuFuncId: string;
184
200
  /** Source type: 'dsl' (serializable), 'complex' (has inline steps), 'graph' */
185
- source: 'dsl' | 'complex' | 'graph' | 'ai-agent';
201
+ source: 'dsl' | 'complex' | 'graph' | 'dynamic-workflow';
186
202
  /** Optional description */
187
203
  description?: string;
188
204
  /** Tags for organization */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/core",
3
- "version": "0.12.14",
3
+ "version": "0.12.15",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "module": "dist/index.js",
@@ -25,6 +25,7 @@
25
25
  "./channel/local": "./dist/wirings/channel/local/index.js",
26
26
  "./channel/serverless": "./dist/wirings/channel/serverless/index.js",
27
27
  "./http": "./dist/wirings/http/index.js",
28
+ "./remote": "./dist/remote.js",
28
29
  "./queue": "./dist/wirings/queue/index.js",
29
30
  "./scheduler": "./dist/wirings/scheduler/index.js",
30
31
  "./trigger": "./dist/wirings/trigger/index.js",
@@ -41,6 +42,7 @@
41
42
  "./oauth2": "./dist/wirings/oauth2/index.js",
42
43
  "./errors": "./dist/errors/index.js",
43
44
  "./services": "./dist/services/index.js",
45
+ "./services/local-meta": "./dist/services/meta-service.js",
44
46
  "./services/local-content": "./dist/services/local-content.js",
45
47
  "./services/gopass-secrets": "./dist/services/gopass-secrets.js",
46
48
  "./crypto-utils": "./dist/crypto-utils.js",
@@ -52,6 +54,7 @@
52
54
  "dependencies": {
53
55
  "@standard-schema/spec": "^1.1.0",
54
56
  "cookie": "^1.1.1",
57
+ "json-schema": "^0.4.0",
55
58
  "path-to-regexp": "^8.3.0",
56
59
  "picoquery": "^2.5.0"
57
60
  },
@@ -15,6 +15,18 @@ addError(PikkuMissingMetaError, {
15
15
  message: 'Required metadata is missing',
16
16
  })
17
17
 
18
+ export class MissingServiceError extends PikkuError {}
19
+ addError(MissingServiceError, {
20
+ status: 500,
21
+ message: 'A required service is not configured',
22
+ })
23
+
24
+ export class LocalEnvironmentOnlyError extends PikkuError {}
25
+ addError(LocalEnvironmentOnlyError, {
26
+ status: 403,
27
+ message: 'This operation is only available in local development mode',
28
+ })
29
+
18
30
  /**
19
31
  * The server cannot or will not process the request due to client error (e.g., malformed request syntax).
20
32
  * @group Error
@@ -333,11 +333,12 @@ export const runPikkuFunc = async <In = any, Out = any>(
333
333
  })
334
334
  }
335
335
 
336
- const wireServices = await resolvedCreateWireServices?.(
337
- resolvedSingletonServices,
338
- resolvedWire
339
- )
336
+ let wireServices: Record<string, unknown> | undefined
340
337
  try {
338
+ wireServices = (await resolvedCreateWireServices?.(
339
+ resolvedSingletonServices,
340
+ resolvedWire
341
+ )) as Record<string, unknown> | undefined
341
342
  const services =
342
343
  wireServices && Object.keys(wireServices).length > 0
343
344
  ? { ...resolvedSingletonServices, ...wireServices }
@@ -257,6 +257,7 @@ export const pikkuAuth = <
257
257
  if (!session) return false
258
258
  return fn(services, session as Session)
259
259
  }
260
+ ;(wrapper as any).__pikkuAuth = true
260
261
  return wrapper as any
261
262
  }
262
263
 
@@ -1,5 +1,15 @@
1
1
  export { addFunction, getAllFunctionNames } from './function-runner.js'
2
+ export {
3
+ pikkuAuth,
4
+ pikkuPermission,
5
+ pikkuPermissionFactory,
6
+ pikkuApprovalDescription,
7
+ } from './functions.types.js'
2
8
  export type {
3
9
  CorePikkuFunction,
4
10
  CorePikkuFunctionSessionless,
11
+ CorePikkuFunctionConfig,
12
+ CorePikkuAuth,
13
+ CorePikkuAuthConfig,
14
+ CorePikkuPermission,
5
15
  } from './functions.types.js'
@@ -91,7 +91,7 @@ describe('handleHTTPError', () => {
91
91
  assert.deepStrictEqual(http._state.jsonBody, {
92
92
  message: 'The server cannot find the requested resource.',
93
93
  payload: undefined,
94
- traceId: 'tracker-1',
94
+ errorId: 'tracker-1',
95
95
  })
96
96
  })
97
97
 
@@ -112,7 +112,7 @@ describe('handleHTTPError', () => {
112
112
 
113
113
  assert.strictEqual(http._state.statusCode, 400)
114
114
  assert.ok(http._state.jsonBody.message.includes('client error'))
115
- assert.strictEqual(http._state.jsonBody.traceId, 'tracker-2')
115
+ assert.strictEqual(http._state.jsonBody.errorId, 'tracker-2')
116
116
  })
117
117
 
118
118
  test('should set status 403 for ForbiddenError', () => {
@@ -9,7 +9,7 @@ import type { PikkuHTTP } from './wirings/http/http.types.js'
9
9
  *
10
10
  * @param {any} e - The error that occurred
11
11
  * @param {PikkuHTTP | undefined} http - HTTP wire object
12
- * @param {string} trackerId - Unique ID for tracking this error
12
+ * @param {string} traceId - Unique ID for tracking this error
13
13
  * @param {Logger} logger - Logger service
14
14
  * @param {number[]} logWarningsForStatusCodes - HTTP status codes to log as warnings
15
15
  * @param {boolean} respondWith404 - Whether to respond with 404 for NotFoundError
@@ -18,7 +18,7 @@ import type { PikkuHTTP } from './wirings/http/http.types.js'
18
18
  export const handleHTTPError = (
19
19
  e: any,
20
20
  http: PikkuHTTP | undefined,
21
- trackerId: string | undefined,
21
+ traceId: string | undefined,
22
22
  logger: Logger,
23
23
  logWarningsForStatusCodes: number[],
24
24
  respondWith404: boolean,
@@ -38,13 +38,13 @@ export const handleHTTPError = (
38
38
  http?.response?.json({
39
39
  message: errorResponse.message,
40
40
  payload: (e as any).payload,
41
- traceId: trackerId,
41
+ errorId: traceId,
42
42
  })
43
43
 
44
44
  // Log certain status codes as warnings
45
45
  if (logWarningsForStatusCodes.includes(errorResponse.status)) {
46
- if (trackerId) {
47
- logger.warn(`Warning id: ${trackerId}`)
46
+ if (traceId) {
47
+ logger.warn(`Warning id: ${traceId}`)
48
48
  }
49
49
  logger.warn(e instanceof Error ? e.message : e)
50
50
  }
@@ -53,9 +53,9 @@ export const handleHTTPError = (
53
53
  logger.error(e instanceof Error ? e.message : e)
54
54
  http?.response?.status(500)
55
55
 
56
- if (trackerId) {
57
- logger.warn(`Error id: ${trackerId}`)
58
- const errorBody: Record<string, unknown> = { errorId: trackerId }
56
+ if (traceId) {
57
+ logger.warn(`Error id: ${traceId}`)
58
+ const errorBody: Record<string, unknown> = { errorId: traceId }
59
59
  if (exposeErrors && !isProduction() && e instanceof Error) {
60
60
  errorBody.message = e.message
61
61
  errorBody.stack = e.stack
package/src/index.ts CHANGED
@@ -103,12 +103,11 @@ export type { AIStorageService } from './services/ai-storage-service.js'
103
103
  export type { HTTPMethod } from './wirings/http/http.types.js'
104
104
  export type { GraphNodeConfig } from './wirings/workflow/graph/workflow-graph.types.js'
105
105
  export { createGraph } from './wirings/workflow/graph/graph-node.js'
106
- export { workflow as wireWorkflow } from './wirings/workflow/workflow-helpers.js'
107
106
  export { wireAddon } from './wirings/rpc/wire-addon.js'
108
107
  export type { WireAddonConfig } from './wirings/rpc/wire-addon.js'
109
108
  export type { PikkuPackageState } from './types/state.types.js'
110
109
  export { runMiddleware, addMiddleware } from './middleware-runner.js'
111
- export { addPermission } from './permissions.js'
110
+ export { addPermission, checkAuthPermissions } from './permissions.js'
112
111
  export { isSerializable, stopSingletonServices } from './utils.js'
113
112
  export { getSingletonServices, getCreateWireServices } from './pikku-state.js'
114
113
  export {
@@ -3,3 +3,5 @@ export { authCookie } from './auth-cookie.js'
3
3
  export { authBearer } from './auth-bearer.js'
4
4
  export { pikkuRemoteAuthMiddleware } from './remote-auth.js'
5
5
  export { cors } from './cors.js'
6
+ export { addMiddleware, runMiddleware } from '../middleware-runner.js'
7
+ export { addPermission } from '../permissions.js'