@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.
- package/CHANGELOG.md +30 -0
- package/dist/errors/errors.d.ts +4 -0
- package/dist/errors/errors.js +12 -0
- package/dist/function/function-runner.js +2 -1
- package/dist/function/functions.types.js +1 -0
- package/dist/function/index.d.ts +2 -1
- package/dist/function/index.js +1 -0
- package/dist/handle-error.d.ts +2 -2
- package/dist/handle-error.js +8 -8
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/middleware/index.d.ts +2 -0
- package/dist/middleware/index.js +2 -0
- package/dist/middleware/remote-auth.js +5 -2
- package/dist/permissions.d.ts +13 -1
- package/dist/permissions.js +51 -0
- package/dist/pikku-state.d.ts +0 -1
- package/dist/pikku-state.js +1 -4
- package/dist/remote.d.ts +10 -0
- package/dist/remote.js +32 -0
- package/dist/schema.js +2 -0
- package/dist/services/deployment-service.d.ts +12 -1
- package/dist/services/in-memory-queue-service.d.ts +7 -0
- package/dist/services/in-memory-queue-service.js +28 -0
- package/dist/services/index.d.ts +4 -1
- package/dist/services/index.js +2 -1
- package/dist/services/logger-console.d.ts +26 -40
- package/dist/services/logger-console.js +102 -46
- package/dist/services/logger.d.ts +5 -0
- package/dist/services/meta-service.d.ts +175 -0
- package/dist/services/meta-service.js +310 -0
- package/dist/services/workflow-service.d.ts +6 -1
- package/dist/testing/service-tests.js +0 -8
- package/dist/types/core.types.d.ts +8 -0
- package/dist/types/state.types.d.ts +2 -2
- package/dist/wirings/ai-agent/ai-agent-prepare.js +42 -21
- package/dist/wirings/ai-agent/ai-agent-registry.js +2 -2
- package/dist/wirings/ai-agent/ai-agent-runner.js +18 -1
- package/dist/wirings/ai-agent/ai-agent-stream.js +1 -0
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +5 -3
- package/dist/wirings/channel/channel-runner.js +3 -3
- package/dist/wirings/cli/cli-runner.js +3 -2
- package/dist/wirings/cli/command-parser.js +7 -3
- package/dist/wirings/http/http-runner.d.ts +1 -1
- package/dist/wirings/http/http-runner.js +23 -11
- package/dist/wirings/http/http.types.d.ts +2 -0
- package/dist/wirings/mcp/mcp-runner.js +5 -3
- package/dist/wirings/queue/queue-runner.d.ts +3 -1
- package/dist/wirings/queue/queue-runner.js +7 -3
- package/dist/wirings/rpc/rpc-runner.js +56 -90
- package/dist/wirings/scheduler/scheduler-runner.d.ts +3 -1
- package/dist/wirings/scheduler/scheduler-runner.js +9 -5
- package/dist/wirings/trigger/trigger-runner.js +4 -2
- package/dist/wirings/workflow/dsl/workflow-runner.d.ts +1 -1
- package/dist/wirings/workflow/dsl/workflow-runner.js +6 -9
- package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -1
- package/dist/wirings/workflow/graph/graph-runner.js +18 -4
- package/dist/wirings/workflow/index.d.ts +4 -2
- package/dist/wirings/workflow/index.js +4 -2
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +16 -4
- package/dist/wirings/workflow/pikku-workflow-service.js +216 -24
- package/dist/wirings/workflow/workflow-queue-workers.d.ts +40 -0
- package/dist/wirings/workflow/workflow-queue-workers.js +41 -0
- package/dist/wirings/workflow/workflow.types.d.ts +17 -1
- package/package.json +4 -1
- package/src/errors/errors.ts +12 -0
- package/src/function/function-runner.ts +5 -4
- package/src/function/functions.types.ts +1 -0
- package/src/function/index.ts +10 -0
- package/src/handle-error.test.ts +2 -2
- package/src/handle-error.ts +8 -8
- package/src/index.ts +1 -2
- package/src/middleware/index.ts +2 -0
- package/src/middleware/remote-auth.test.ts +4 -4
- package/src/middleware/remote-auth.ts +7 -2
- package/src/permissions.ts +70 -0
- package/src/pikku-state.test.ts +0 -26
- package/src/pikku-state.ts +1 -5
- package/src/remote.ts +46 -0
- package/src/schema.ts +1 -0
- package/src/services/deployment-service.ts +17 -1
- package/src/services/in-memory-queue-service.ts +46 -0
- package/src/services/index.ts +21 -1
- package/src/services/logger-console.ts +127 -47
- package/src/services/logger.ts +6 -0
- package/src/services/meta-service.ts +523 -0
- package/src/services/workflow-service.ts +8 -0
- package/src/testing/service-tests.ts +0 -10
- package/src/types/core.types.ts +8 -0
- package/src/types/state.types.ts +2 -2
- package/src/wirings/ai-agent/ai-agent-prepare.ts +51 -40
- package/src/wirings/ai-agent/ai-agent-registry.ts +3 -3
- package/src/wirings/ai-agent/ai-agent-runner.ts +16 -1
- package/src/wirings/ai-agent/ai-agent-stream.ts +8 -0
- package/src/wirings/ai-agent/ai-agent.types.ts +5 -3
- package/src/wirings/channel/channel-runner.ts +4 -5
- package/src/wirings/cli/cli-runner.test.ts +8 -7
- package/src/wirings/cli/cli-runner.ts +4 -3
- package/src/wirings/cli/command-parser.ts +8 -3
- package/src/wirings/http/http-runner.ts +27 -11
- package/src/wirings/http/http.types.ts +2 -0
- package/src/wirings/mcp/mcp-runner.ts +7 -9
- package/src/wirings/queue/queue-runner.test.ts +5 -11
- package/src/wirings/queue/queue-runner.ts +11 -3
- package/src/wirings/rpc/rpc-runner.ts +82 -115
- package/src/wirings/scheduler/scheduler-runner.test.ts +5 -11
- package/src/wirings/scheduler/scheduler-runner.ts +13 -5
- package/src/wirings/trigger/trigger-runner.test.ts +10 -11
- package/src/wirings/trigger/trigger-runner.ts +6 -4
- package/src/wirings/workflow/dsl/workflow-runner.ts +11 -10
- package/src/wirings/workflow/graph/graph-runner.ts +19 -4
- package/src/wirings/workflow/index.ts +17 -6
- package/src/wirings/workflow/pikku-workflow-service.ts +284 -24
- package/src/wirings/workflow/workflow-queue-workers.ts +85 -0
- package/src/wirings/workflow/workflow.types.ts +16 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/wirings/ai-agent/agent-dynamic-workflow.d.ts +0 -6
- package/dist/wirings/ai-agent/agent-dynamic-workflow.js +0 -468
- package/dist/wirings/workflow/workflow-helpers.d.ts +0 -36
- package/dist/wirings/workflow/workflow-helpers.js +0 -38
- package/src/wirings/ai-agent/agent-dynamic-workflow.ts +0 -610
- package/src/wirings/workflow/workflow-helpers.test.ts +0 -129
- 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(
|
|
551
|
+
public async resumeWorkflow(
|
|
552
|
+
runId: string,
|
|
553
|
+
workflowName?: string
|
|
554
|
+
): Promise<void> {
|
|
383
555
|
const queueService = this.verifyQueueService()
|
|
384
|
-
|
|
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.
|
|
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.
|
|
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
|
-
//
|
|
451
|
-
const
|
|
452
|
-
|
|
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 (
|
|
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(
|
|
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
|
|
563
|
-
const workflowMeta = meta
|
|
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 (
|
|
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
|
-
|
|
592
|
-
|
|
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 === '
|
|
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.
|
|
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
|
-
|
|
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' | '
|
|
260
|
+
source: 'dsl' | 'complex' | 'graph' | 'dynamic-workflow'
|
|
246
261
|
/** Optional description */
|
|
247
262
|
description?: string
|
|
248
263
|
/** Tags for organization */
|