@pikku/core 0.12.20 → 0.12.22
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 +17 -0
- package/dist/dev/hot-reload.js +20 -6
- package/dist/errors/error-handler.d.ts +1 -1
- package/dist/errors/error-handler.js +2 -2
- package/dist/function/functions.types.d.ts +3 -2
- package/dist/handle-error.js +3 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/middleware/index.d.ts +2 -2
- package/dist/middleware/index.js +2 -2
- package/dist/middleware/telemetry.d.ts +2 -2
- package/dist/middleware/telemetry.js +2 -2
- package/dist/middleware-runner.d.ts +15 -27
- package/dist/middleware-runner.js +25 -30
- package/dist/permissions.d.ts +15 -22
- package/dist/permissions.js +30 -25
- package/dist/pikku-request.js +1 -1
- package/dist/pikku-state.js +2 -3
- package/dist/services/ai-agent-runner-service.d.ts +1 -0
- package/dist/services/in-memory-queue-service.js +1 -1
- package/dist/services/in-memory-workflow-service.d.ts +15 -13
- package/dist/services/in-memory-workflow-service.js +31 -13
- package/dist/services/local-content.js +8 -1
- package/dist/services/user-session-service.d.ts +1 -1
- package/dist/testing/service-tests.js +24 -0
- package/dist/types/core.types.d.ts +4 -0
- package/dist/types/state.types.d.ts +2 -18
- package/dist/wirings/ai-agent/ai-agent-memory.d.ts +24 -5
- package/dist/wirings/ai-agent/ai-agent-memory.js +128 -23
- package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +10 -1
- package/dist/wirings/ai-agent/ai-agent-model-config.js +15 -35
- package/dist/wirings/ai-agent/ai-agent-prepare.js +4 -1
- package/dist/wirings/ai-agent/ai-agent-runner.js +45 -31
- package/dist/wirings/ai-agent/ai-agent-stream.js +63 -34
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +20 -0
- package/dist/wirings/channel/channel-handler.js +1 -1
- package/dist/wirings/channel/channel-store.d.ts +13 -0
- package/dist/wirings/channel/channel.types.d.ts +3 -0
- package/dist/wirings/channel/local/local-channel-runner.js +23 -5
- package/dist/wirings/channel/pikku-abstract-channel-handler.js +8 -0
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +9 -0
- package/dist/wirings/cli/cli-runner.js +24 -5
- package/dist/wirings/cli/command-parser.js +19 -4
- package/dist/wirings/http/http-runner.js +72 -36
- package/dist/wirings/http/http.types.d.ts +1 -0
- package/dist/wirings/http/pikku-fetch-http-request.js +3 -1
- package/dist/wirings/http/web-request.js +32 -0
- package/dist/wirings/rpc/rpc-runner.js +13 -3
- package/dist/wirings/workflow/graph/graph-node.d.ts +8 -0
- package/dist/wirings/workflow/graph/graph-node.js +4 -0
- package/dist/wirings/workflow/graph/graph-runner.js +12 -10
- package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +4 -0
- package/dist/wirings/workflow/index.d.ts +1 -1
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +81 -16
- package/dist/wirings/workflow/pikku-workflow-service.js +266 -45
- package/dist/wirings/workflow/workflow.types.d.ts +34 -0
- package/package.json +1 -1
- package/run-tests.sh +4 -1
- package/src/dev/hot-reload.test.ts +62 -11
- package/src/dev/hot-reload.ts +28 -7
- package/src/errors/error-handler.ts +8 -2
- package/src/errors/error.test.ts +2 -0
- package/src/function/function-runner.test.ts +500 -10
- package/src/function/functions.types.ts +3 -2
- package/src/handle-error.test.ts +1 -1
- package/src/handle-error.ts +4 -1
- package/src/index.ts +12 -2
- package/src/middleware/index.ts +11 -2
- package/src/middleware/telemetry.ts +2 -2
- package/src/middleware-runner.test.ts +16 -16
- package/src/middleware-runner.ts +42 -30
- package/src/permissions.test.ts +27 -24
- package/src/permissions.ts +41 -25
- package/src/pikku-request.test.ts +35 -0
- package/src/pikku-request.ts +1 -1
- package/src/pikku-state.ts +2 -3
- package/src/run-tests-script.test.ts +18 -0
- package/src/services/ai-agent-runner-service.ts +1 -0
- package/src/services/in-memory-queue-service.ts +1 -1
- package/src/services/in-memory-session-store.ts +1 -2
- package/src/services/in-memory-workflow-service.test.ts +33 -11
- package/src/services/in-memory-workflow-service.ts +49 -13
- package/src/services/local-content.test.ts +54 -0
- package/src/services/local-content.ts +12 -1
- package/src/services/typed-credential-service.ts +3 -3
- package/src/services/typed-secret-service.ts +3 -3
- package/src/services/typed-variables-service.ts +3 -3
- package/src/services/user-session-service.ts +4 -4
- package/src/testing/service-tests.ts +30 -0
- package/src/types/core.types.ts +6 -2
- package/src/types/state.types.ts +2 -13
- package/src/wirings/ai-agent/ai-agent-memory.test.ts +324 -0
- package/src/wirings/ai-agent/ai-agent-memory.ts +187 -36
- package/src/wirings/ai-agent/ai-agent-model-config.test.ts +12 -90
- package/src/wirings/ai-agent/ai-agent-model-config.ts +14 -38
- package/src/wirings/ai-agent/ai-agent-prepare.test.ts +292 -0
- package/src/wirings/ai-agent/ai-agent-prepare.ts +4 -1
- package/src/wirings/ai-agent/ai-agent-registry.test.ts +230 -3
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +625 -6
- package/src/wirings/ai-agent/ai-agent-runner.ts +65 -50
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +544 -5
- package/src/wirings/ai-agent/ai-agent-stream.ts +71 -69
- package/src/wirings/ai-agent/ai-agent.types.ts +24 -0
- package/src/wirings/channel/channel-handler.test.ts +272 -0
- package/src/wirings/channel/channel-handler.ts +1 -1
- package/src/wirings/channel/channel-middleware-runner.test.ts +163 -0
- package/src/wirings/channel/channel-store.ts +19 -0
- package/src/wirings/channel/channel.types.ts +4 -0
- package/src/wirings/channel/local/local-channel-runner.test.ts +63 -0
- package/src/wirings/channel/local/local-channel-runner.ts +41 -5
- package/src/wirings/channel/local/local-eventhub-service.ts +3 -3
- package/src/wirings/channel/pikku-abstract-channel-handler.ts +9 -2
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +23 -5
- package/src/wirings/cli/channel/cli-raw-channel-runner.ts +7 -2
- package/src/wirings/cli/cli-runner.test.ts +255 -2
- package/src/wirings/cli/cli-runner.ts +31 -10
- package/src/wirings/cli/cli.types.ts +4 -8
- package/src/wirings/cli/command-parser.test.ts +83 -0
- package/src/wirings/cli/command-parser.ts +23 -4
- package/src/wirings/http/http-runner.test.ts +296 -1
- package/src/wirings/http/http-runner.ts +87 -57
- package/src/wirings/http/http.types.ts +11 -26
- package/src/wirings/http/pikku-fetch-http-request.ts +8 -4
- package/src/wirings/http/pikku-fetch-http-response.test.ts +41 -0
- package/src/wirings/http/web-request.test.ts +115 -0
- package/src/wirings/http/web-request.ts +43 -5
- package/src/wirings/mcp/mcp-runner.test.ts +367 -0
- package/src/wirings/queue/queue.types.ts +2 -5
- package/src/wirings/rpc/rpc-runner.test.ts +511 -0
- package/src/wirings/rpc/rpc-runner.ts +15 -3
- package/src/wirings/rpc/wire-addon.test.ts +57 -0
- package/src/wirings/workflow/graph/graph-node.ts +12 -0
- package/src/wirings/workflow/graph/graph-runner.test.ts +98 -0
- package/src/wirings/workflow/graph/graph-runner.ts +28 -10
- package/src/wirings/workflow/graph/workflow-graph.types.ts +4 -0
- package/src/wirings/workflow/index.ts +1 -0
- package/src/wirings/workflow/pikku-workflow-service.test.ts +19 -2
- package/src/wirings/workflow/pikku-workflow-service.ts +370 -71
- package/src/wirings/workflow/workflow-queue-workers.test.ts +131 -0
- package/src/wirings/workflow/workflow.types.ts +68 -5
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -51,6 +51,7 @@ import type {
|
|
|
51
51
|
StepStatus,
|
|
52
52
|
WorkflowPlannedStep,
|
|
53
53
|
WorkflowRun,
|
|
54
|
+
WorkflowRunMirror,
|
|
54
55
|
WorkflowRunStatus,
|
|
55
56
|
WorkflowRunWire,
|
|
56
57
|
WorkflowStatus,
|
|
@@ -131,6 +132,18 @@ addError(WorkflowRunNotFoundError, {
|
|
|
131
132
|
message: 'Workflow run not found.',
|
|
132
133
|
})
|
|
133
134
|
|
|
135
|
+
export class WorkflowRunFailedError extends PikkuError {
|
|
136
|
+
public payload: { message?: string }
|
|
137
|
+
constructor(message?: string) {
|
|
138
|
+
super(`Workflow run failed: ${message ?? 'unknown'}`)
|
|
139
|
+
this.payload = { message }
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
addError(WorkflowRunFailedError, {
|
|
143
|
+
status: 422,
|
|
144
|
+
message: 'Workflow run failed.',
|
|
145
|
+
})
|
|
146
|
+
|
|
134
147
|
export class WorkflowServiceNotInitialized extends Error {}
|
|
135
148
|
export class WorkflowStepNameNotString extends Error {
|
|
136
149
|
constructor(stepName: any) {
|
|
@@ -157,7 +170,43 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
157
170
|
return getSingletonServices()?.logger
|
|
158
171
|
}
|
|
159
172
|
|
|
160
|
-
|
|
173
|
+
protected mirror?: WorkflowRunMirror
|
|
174
|
+
|
|
175
|
+
constructor(
|
|
176
|
+
options: { wireQueues?: boolean; mirror?: WorkflowRunMirror } = {}
|
|
177
|
+
) {
|
|
178
|
+
const wireQueues = options.wireQueues ?? true
|
|
179
|
+
this.mirror = options.mirror
|
|
180
|
+
if (wireQueues) {
|
|
181
|
+
this.wireQueueWorkers()
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private async safeMirror(fn: () => Promise<void>): Promise<void> {
|
|
186
|
+
if (!this.mirror) return
|
|
187
|
+
try {
|
|
188
|
+
await fn()
|
|
189
|
+
} catch (err: any) {
|
|
190
|
+
try {
|
|
191
|
+
this.logger?.warn?.(
|
|
192
|
+
`[pikku] WorkflowRunMirror write failed: ${err?.message ?? err}`
|
|
193
|
+
)
|
|
194
|
+
} catch {
|
|
195
|
+
// logger unavailable (e.g. singleton services not initialized) — swallow
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
public rewireQueueWorkers(): void {
|
|
201
|
+
this.wireQueueWorkers()
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Wire the queue-based orchestrator/step/sleeper workers.
|
|
206
|
+
* Subclasses that orchestrate without queues (e.g. Durable Objects) should
|
|
207
|
+
* pass `wireQueues: false` to the base constructor and skip this entirely.
|
|
208
|
+
*/
|
|
209
|
+
protected wireQueueWorkers(): void {
|
|
161
210
|
const functions = pikkuState(null, 'function', 'functions')
|
|
162
211
|
const functionsMeta = pikkuState(null, 'function', 'meta')
|
|
163
212
|
|
|
@@ -237,6 +286,9 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
237
286
|
func: pikkuWorkflowSleeperFunc,
|
|
238
287
|
})
|
|
239
288
|
}
|
|
289
|
+
if (!functionsMeta.pikkuWorkflowSleeper) {
|
|
290
|
+
functionsMeta.pikkuWorkflowSleeper = mkMeta('pikkuWorkflowSleeper')
|
|
291
|
+
}
|
|
240
292
|
}
|
|
241
293
|
|
|
242
294
|
/**
|
|
@@ -268,7 +320,40 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
268
320
|
}
|
|
269
321
|
}
|
|
270
322
|
|
|
271
|
-
|
|
323
|
+
public async createRun(
|
|
324
|
+
workflowName: string,
|
|
325
|
+
input: any,
|
|
326
|
+
inline: boolean,
|
|
327
|
+
graphHash: string,
|
|
328
|
+
wire: WorkflowRunWire,
|
|
329
|
+
options?: {
|
|
330
|
+
deterministic?: boolean
|
|
331
|
+
plannedSteps?: WorkflowPlannedStep[]
|
|
332
|
+
}
|
|
333
|
+
): Promise<string> {
|
|
334
|
+
const runId = await this.createRunImpl(
|
|
335
|
+
workflowName,
|
|
336
|
+
input,
|
|
337
|
+
inline,
|
|
338
|
+
graphHash,
|
|
339
|
+
wire,
|
|
340
|
+
options
|
|
341
|
+
)
|
|
342
|
+
await this.safeMirror(() =>
|
|
343
|
+
this.mirror!.createRun(
|
|
344
|
+
runId,
|
|
345
|
+
workflowName,
|
|
346
|
+
input,
|
|
347
|
+
inline,
|
|
348
|
+
graphHash,
|
|
349
|
+
wire,
|
|
350
|
+
options
|
|
351
|
+
)
|
|
352
|
+
)
|
|
353
|
+
return runId
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
protected abstract createRunImpl(
|
|
272
357
|
workflowName: string,
|
|
273
358
|
input: any,
|
|
274
359
|
inline: boolean,
|
|
@@ -352,7 +437,19 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
352
437
|
* @param id - Run ID
|
|
353
438
|
* @param status - New status
|
|
354
439
|
*/
|
|
355
|
-
|
|
440
|
+
public async updateRunStatus(
|
|
441
|
+
id: string,
|
|
442
|
+
status: WorkflowStatus,
|
|
443
|
+
output?: any,
|
|
444
|
+
error?: SerializedError
|
|
445
|
+
): Promise<void> {
|
|
446
|
+
await this.updateRunStatusImpl(id, status, output, error)
|
|
447
|
+
await this.safeMirror(() =>
|
|
448
|
+
this.mirror!.updateRunStatus(id, status, output, error)
|
|
449
|
+
)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
protected abstract updateRunStatusImpl(
|
|
356
453
|
id: string,
|
|
357
454
|
status: WorkflowStatus,
|
|
358
455
|
output?: any,
|
|
@@ -369,7 +466,27 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
369
466
|
* @param stepOptions - Step options (retries, retryDelay)
|
|
370
467
|
* @returns Step state with generated stepId
|
|
371
468
|
*/
|
|
372
|
-
|
|
469
|
+
public async insertStepState(
|
|
470
|
+
runId: string,
|
|
471
|
+
stepName: string,
|
|
472
|
+
rpcName: string | null,
|
|
473
|
+
data: any,
|
|
474
|
+
stepOptions?: WorkflowStepOptions
|
|
475
|
+
): Promise<StepState> {
|
|
476
|
+
const step = await this.insertStepStateImpl(
|
|
477
|
+
runId,
|
|
478
|
+
stepName,
|
|
479
|
+
rpcName,
|
|
480
|
+
data,
|
|
481
|
+
stepOptions
|
|
482
|
+
)
|
|
483
|
+
await this.safeMirror(() =>
|
|
484
|
+
this.mirror!.insertStepState(runId, { ...step, stepName, rpcName, data })
|
|
485
|
+
)
|
|
486
|
+
return step
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
protected abstract insertStepStateImpl(
|
|
373
490
|
runId: string,
|
|
374
491
|
stepName: string,
|
|
375
492
|
rpcName: string | null,
|
|
@@ -390,14 +507,24 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
390
507
|
* Updates both workflow_step and workflow_step_history
|
|
391
508
|
* @param stepId - Step ID
|
|
392
509
|
*/
|
|
393
|
-
|
|
510
|
+
public async setStepRunning(stepId: string): Promise<void> {
|
|
511
|
+
await this.setStepRunningImpl(stepId)
|
|
512
|
+
await this.safeMirror(() => this.mirror!.setStepRunning(stepId))
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
protected abstract setStepRunningImpl(stepId: string): Promise<void>
|
|
394
516
|
|
|
395
517
|
/**
|
|
396
518
|
* Mark step as scheduled (queued for execution)
|
|
397
519
|
* Updates both workflow_step and workflow_step_history
|
|
398
520
|
* @param stepId - Step ID
|
|
399
521
|
*/
|
|
400
|
-
|
|
522
|
+
public async setStepScheduled(stepId: string): Promise<void> {
|
|
523
|
+
await this.setStepScheduledImpl(stepId)
|
|
524
|
+
await this.safeMirror(() => this.mirror!.setStepScheduled(stepId))
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
protected abstract setStepScheduledImpl(stepId: string): Promise<void>
|
|
401
528
|
|
|
402
529
|
/**
|
|
403
530
|
* Store step result and mark as succeeded
|
|
@@ -405,14 +532,35 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
405
532
|
* @param stepId - Step ID
|
|
406
533
|
* @param result - Step result
|
|
407
534
|
*/
|
|
408
|
-
|
|
535
|
+
public async setStepResult(stepId: string, result: any): Promise<void> {
|
|
536
|
+
await this.setStepResultImpl(stepId, result)
|
|
537
|
+
await this.safeMirror(() => this.mirror!.setStepResult(stepId, result))
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
protected abstract setStepResultImpl(
|
|
541
|
+
stepId: string,
|
|
542
|
+
result: any
|
|
543
|
+
): Promise<void>
|
|
409
544
|
|
|
410
545
|
/**
|
|
411
546
|
* Set the child workflow run ID on a step
|
|
412
547
|
* @param stepId - Step ID
|
|
413
548
|
* @param childRunId - Child workflow run ID
|
|
414
549
|
*/
|
|
415
|
-
|
|
550
|
+
public async setStepChildRunId(
|
|
551
|
+
stepId: string,
|
|
552
|
+
childRunId: string
|
|
553
|
+
): Promise<void> {
|
|
554
|
+
await this.setStepChildRunIdImpl(stepId, childRunId)
|
|
555
|
+
await this.safeMirror(() =>
|
|
556
|
+
this.mirror!.setStepChildRunId(stepId, childRunId)
|
|
557
|
+
)
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
protected abstract setStepChildRunIdImpl(
|
|
561
|
+
stepId: string,
|
|
562
|
+
childRunId: string
|
|
563
|
+
): Promise<void>
|
|
416
564
|
|
|
417
565
|
/**
|
|
418
566
|
* Store step error and mark as failed
|
|
@@ -420,7 +568,20 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
420
568
|
* @param stepId - Step ID
|
|
421
569
|
* @param error - Error object
|
|
422
570
|
*/
|
|
423
|
-
|
|
571
|
+
public async setStepError(stepId: string, error: Error): Promise<void> {
|
|
572
|
+
await this.setStepErrorImpl(stepId, error)
|
|
573
|
+
const serialized: SerializedError = {
|
|
574
|
+
message: error.message,
|
|
575
|
+
stack: error.stack,
|
|
576
|
+
code: (error as any).code,
|
|
577
|
+
}
|
|
578
|
+
await this.safeMirror(() => this.mirror!.setStepError(stepId, serialized))
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
protected abstract setStepErrorImpl(
|
|
582
|
+
stepId: string,
|
|
583
|
+
error: Error
|
|
584
|
+
): Promise<void>
|
|
424
585
|
|
|
425
586
|
/**
|
|
426
587
|
* Create a new retry attempt for a failed step
|
|
@@ -430,7 +591,22 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
430
591
|
* @param failedStepId - Failed step ID to copy from
|
|
431
592
|
* @returns New step state for the retry attempt
|
|
432
593
|
*/
|
|
433
|
-
|
|
594
|
+
public async createRetryAttempt(
|
|
595
|
+
failedStepId: string,
|
|
596
|
+
status: 'pending' | 'running'
|
|
597
|
+
): Promise<StepState> {
|
|
598
|
+
const newStep = await this.createRetryAttemptImpl(failedStepId, status)
|
|
599
|
+
const stepName = (newStep as any).stepName ?? ''
|
|
600
|
+
await this.safeMirror(() =>
|
|
601
|
+
this.mirror!.createRetryAttempt(failedStepId, {
|
|
602
|
+
...newStep,
|
|
603
|
+
stepName,
|
|
604
|
+
})
|
|
605
|
+
)
|
|
606
|
+
return newStep
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
protected abstract createRetryAttemptImpl(
|
|
434
610
|
failedStepId: string,
|
|
435
611
|
status: 'pending' | 'running'
|
|
436
612
|
): Promise<StepState>
|
|
@@ -503,7 +679,18 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
503
679
|
* @param stepId - Step ID
|
|
504
680
|
* @param branchKey - Branch key selected by graph.branch()
|
|
505
681
|
*/
|
|
506
|
-
|
|
682
|
+
public async setBranchTaken(
|
|
683
|
+
stepId: string,
|
|
684
|
+
branchKey: string
|
|
685
|
+
): Promise<void> {
|
|
686
|
+
await this.setBranchTakenImpl(stepId, branchKey)
|
|
687
|
+
await this.safeMirror(() => this.mirror!.setBranchTaken(stepId, branchKey))
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
protected abstract setBranchTakenImpl(
|
|
691
|
+
stepId: string,
|
|
692
|
+
branchKey: string
|
|
693
|
+
): Promise<void>
|
|
507
694
|
|
|
508
695
|
/**
|
|
509
696
|
* Update a state variable in the workflow run's state
|
|
@@ -511,7 +698,16 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
511
698
|
* @param name - Variable name
|
|
512
699
|
* @param value - Value to store
|
|
513
700
|
*/
|
|
514
|
-
|
|
701
|
+
public async updateRunState(
|
|
702
|
+
runId: string,
|
|
703
|
+
name: string,
|
|
704
|
+
value: unknown
|
|
705
|
+
): Promise<void> {
|
|
706
|
+
await this.updateRunStateImpl(runId, name, value)
|
|
707
|
+
await this.safeMirror(() => this.mirror!.updateRunState(runId, name, value))
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
protected abstract updateRunStateImpl(
|
|
515
711
|
runId: string,
|
|
516
712
|
name: string,
|
|
517
713
|
value: unknown
|
|
@@ -524,7 +720,20 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
524
720
|
*/
|
|
525
721
|
abstract getRunState(runId: string): Promise<Record<string, unknown>>
|
|
526
722
|
|
|
527
|
-
|
|
723
|
+
public async upsertWorkflowVersion(
|
|
724
|
+
name: string,
|
|
725
|
+
graphHash: string,
|
|
726
|
+
graph: any,
|
|
727
|
+
source: string,
|
|
728
|
+
status?: WorkflowVersionStatus
|
|
729
|
+
): Promise<void> {
|
|
730
|
+
await this.upsertWorkflowVersionImpl(name, graphHash, graph, source, status)
|
|
731
|
+
await this.safeMirror(() =>
|
|
732
|
+
this.mirror!.upsertWorkflowVersion(name, graphHash, graph, source, status)
|
|
733
|
+
)
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
protected abstract upsertWorkflowVersionImpl(
|
|
528
737
|
name: string,
|
|
529
738
|
graphHash: string,
|
|
530
739
|
graph: any,
|
|
@@ -532,7 +741,18 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
532
741
|
status?: WorkflowVersionStatus
|
|
533
742
|
): Promise<void>
|
|
534
743
|
|
|
535
|
-
|
|
744
|
+
public async updateWorkflowVersionStatus(
|
|
745
|
+
name: string,
|
|
746
|
+
graphHash: string,
|
|
747
|
+
status: WorkflowVersionStatus
|
|
748
|
+
): Promise<void> {
|
|
749
|
+
await this.updateWorkflowVersionStatusImpl(name, graphHash, status)
|
|
750
|
+
await this.safeMirror(() =>
|
|
751
|
+
this.mirror!.updateWorkflowVersionStatus(name, graphHash, status)
|
|
752
|
+
)
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
protected abstract updateWorkflowVersionStatusImpl(
|
|
536
756
|
name: string,
|
|
537
757
|
graphHash: string,
|
|
538
758
|
status: WorkflowVersionStatus
|
|
@@ -573,9 +793,12 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
573
793
|
runId: string,
|
|
574
794
|
stepName: string,
|
|
575
795
|
rpcName: string,
|
|
576
|
-
data: any
|
|
796
|
+
data: any,
|
|
797
|
+
stepOptions?: WorkflowStepOptions
|
|
577
798
|
): Promise<void> {
|
|
578
799
|
const queueService = this.verifyQueueService()
|
|
800
|
+
const retries = stepOptions?.retries ?? 0
|
|
801
|
+
const retryDelay = stepOptions?.retryDelay
|
|
579
802
|
await queueService.add(
|
|
580
803
|
this.getStepWorkerQueueName(rpcName),
|
|
581
804
|
JSON.parse(
|
|
@@ -585,7 +808,18 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
585
808
|
rpcName,
|
|
586
809
|
data,
|
|
587
810
|
})
|
|
588
|
-
)
|
|
811
|
+
),
|
|
812
|
+
retries > 0 || retryDelay
|
|
813
|
+
? {
|
|
814
|
+
attempts: retries + 1,
|
|
815
|
+
backoff:
|
|
816
|
+
typeof retryDelay === 'number'
|
|
817
|
+
? { type: 'fixed', delay: retryDelay }
|
|
818
|
+
: retryDelay === 'exponential'
|
|
819
|
+
? 'exponential'
|
|
820
|
+
: undefined,
|
|
821
|
+
}
|
|
822
|
+
: undefined
|
|
589
823
|
)
|
|
590
824
|
}
|
|
591
825
|
|
|
@@ -624,6 +858,81 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
624
858
|
)
|
|
625
859
|
}
|
|
626
860
|
|
|
861
|
+
/**
|
|
862
|
+
* Dispatch a workflow step to be executed asynchronously.
|
|
863
|
+
*
|
|
864
|
+
* Default implementation enqueues a step worker job via the queue service.
|
|
865
|
+
* Subclasses with non-queue transports (e.g. Durable Objects) override this
|
|
866
|
+
* to dispatch via their own mechanism (RPC to a step worker, etc.).
|
|
867
|
+
*
|
|
868
|
+
* On return, the workflow is paused via `WorkflowAsyncException` thrown by
|
|
869
|
+
* the caller; the step transport is responsible for calling back into the
|
|
870
|
+
* orchestrator (via `resumeWorkflow` or equivalent) when the step completes.
|
|
871
|
+
*
|
|
872
|
+
* @returns true if dispatch was async (caller should pause), false to fall
|
|
873
|
+
* through to the inline execution path.
|
|
874
|
+
*/
|
|
875
|
+
protected async dispatchStep(
|
|
876
|
+
runId: string,
|
|
877
|
+
stepName: string,
|
|
878
|
+
rpcName: string,
|
|
879
|
+
data: unknown,
|
|
880
|
+
stepOptions?: WorkflowStepOptions
|
|
881
|
+
): Promise<boolean> {
|
|
882
|
+
if (this.isInline(runId) || !getSingletonServices()?.queueService) {
|
|
883
|
+
return false
|
|
884
|
+
}
|
|
885
|
+
const retries = stepOptions?.retries ?? 0
|
|
886
|
+
const retryDelay = stepOptions?.retryDelay
|
|
887
|
+
await getSingletonServices()!.queueService!.add(
|
|
888
|
+
this.getStepWorkerQueueName(rpcName),
|
|
889
|
+
JSON.parse(
|
|
890
|
+
JSON.stringify({
|
|
891
|
+
runId,
|
|
892
|
+
stepName,
|
|
893
|
+
rpcName,
|
|
894
|
+
data,
|
|
895
|
+
})
|
|
896
|
+
),
|
|
897
|
+
{
|
|
898
|
+
attempts: retries + 1,
|
|
899
|
+
backoff:
|
|
900
|
+
typeof retryDelay === 'number'
|
|
901
|
+
? { type: 'fixed', delay: retryDelay }
|
|
902
|
+
: retryDelay === 'exponential'
|
|
903
|
+
? 'exponential'
|
|
904
|
+
: undefined,
|
|
905
|
+
}
|
|
906
|
+
)
|
|
907
|
+
return true
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Schedule a workflow sleep wakeup at the given duration.
|
|
912
|
+
*
|
|
913
|
+
* Default implementation uses the scheduler service to enqueue a delayed
|
|
914
|
+
* sleeper RPC. Subclasses with native timer primitives (e.g. Durable Object
|
|
915
|
+
* alarms) override this to schedule directly without going through queues.
|
|
916
|
+
*
|
|
917
|
+
* @returns true if the wakeup was scheduled remotely (caller should pause),
|
|
918
|
+
* false to fall through to inline `setTimeout` behavior.
|
|
919
|
+
*/
|
|
920
|
+
protected async scheduleSleep(
|
|
921
|
+
runId: string,
|
|
922
|
+
stepId: string,
|
|
923
|
+
duration: number | string
|
|
924
|
+
): Promise<boolean> {
|
|
925
|
+
if (this.isInline(runId) || !getSingletonServices()?.schedulerService) {
|
|
926
|
+
return false
|
|
927
|
+
}
|
|
928
|
+
await getSingletonServices()!.schedulerService!.scheduleRPC(
|
|
929
|
+
duration,
|
|
930
|
+
this.getConfig().sleeperRPCName,
|
|
931
|
+
{ runId, stepId }
|
|
932
|
+
)
|
|
933
|
+
return true
|
|
934
|
+
}
|
|
935
|
+
|
|
627
936
|
/**
|
|
628
937
|
* Start a new workflow run
|
|
629
938
|
* Automatically detects workflow type (DSL or graph) from meta and executes accordingly
|
|
@@ -713,10 +1022,16 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
713
1022
|
error.name !== 'WorkflowCancelledException' &&
|
|
714
1023
|
error.name !== 'WorkflowSuspendedException'
|
|
715
1024
|
) {
|
|
1025
|
+
await this.updateRunStatus(runId, 'failed', undefined, {
|
|
1026
|
+
name: error.name,
|
|
1027
|
+
message: error.message,
|
|
1028
|
+
stack: error.stack,
|
|
1029
|
+
})
|
|
716
1030
|
getSingletonServices()!.logger.error(
|
|
717
1031
|
`Workflow ${name} (run ${runId}) failed:`,
|
|
718
1032
|
error
|
|
719
1033
|
)
|
|
1034
|
+
throw error
|
|
720
1035
|
}
|
|
721
1036
|
} finally {
|
|
722
1037
|
this.inlineRuns.delete(runId)
|
|
@@ -749,7 +1064,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
749
1064
|
}
|
|
750
1065
|
if (WORKFLOW_END_STATES.has(run.status)) {
|
|
751
1066
|
if (run.status === 'failed') {
|
|
752
|
-
throw new
|
|
1067
|
+
throw new WorkflowRunFailedError(run.error?.message)
|
|
753
1068
|
}
|
|
754
1069
|
if (run.status === 'cancelled') {
|
|
755
1070
|
throw new Error('Workflow was cancelled')
|
|
@@ -1194,38 +1509,21 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1194
1509
|
// Step is pending - schedule it
|
|
1195
1510
|
await this.setStepScheduled(stepState.stepId)
|
|
1196
1511
|
|
|
1197
|
-
//
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
runId,
|
|
1208
|
-
stepName,
|
|
1209
|
-
rpcName,
|
|
1210
|
-
data,
|
|
1211
|
-
})
|
|
1212
|
-
),
|
|
1213
|
-
{
|
|
1214
|
-
// attempts includes initial attempt, retries doesn't
|
|
1215
|
-
attempts: retries + 1,
|
|
1216
|
-
// Map retry delay to backoff
|
|
1217
|
-
backoff:
|
|
1218
|
-
typeof retryDelay === 'number'
|
|
1219
|
-
? { type: 'fixed', delay: retryDelay }
|
|
1220
|
-
: retryDelay === 'exponential'
|
|
1221
|
-
? 'exponential'
|
|
1222
|
-
: undefined,
|
|
1223
|
-
}
|
|
1224
|
-
)
|
|
1225
|
-
// Pause workflow - step will callback when done
|
|
1512
|
+
// Hand off to subclass-overridable transport. Default behavior enqueues
|
|
1513
|
+
// via the queue service; DO-style subclasses RPC to a step worker.
|
|
1514
|
+
const dispatched = await this.dispatchStep(
|
|
1515
|
+
runId,
|
|
1516
|
+
stepName,
|
|
1517
|
+
rpcName,
|
|
1518
|
+
data,
|
|
1519
|
+
stepOptions
|
|
1520
|
+
)
|
|
1521
|
+
if (dispatched) {
|
|
1226
1522
|
throw new WorkflowAsyncException(runId, stepName)
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
{
|
|
1526
|
+
// Inline (no transport available) - execute locally with retry loop
|
|
1229
1527
|
const retries = stepOptions?.retries ?? 0
|
|
1230
1528
|
const retryDelay = stepOptions?.retryDelay
|
|
1231
1529
|
let currentStepState = stepState
|
|
@@ -1436,27 +1734,23 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1436
1734
|
// Step is pending - schedule it
|
|
1437
1735
|
await this.setStepScheduled(stepState.stepId)
|
|
1438
1736
|
|
|
1439
|
-
//
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
}
|
|
1449
|
-
)
|
|
1450
|
-
// Pause workflow - sleep will callback when done
|
|
1737
|
+
// Hand off to subclass-overridable transport. Default behavior schedules
|
|
1738
|
+
// a delayed sleeper RPC via the scheduler service; DO-style subclasses
|
|
1739
|
+
// override to use native timer primitives (e.g. setAlarm).
|
|
1740
|
+
const scheduled = await this.scheduleSleep(
|
|
1741
|
+
runId,
|
|
1742
|
+
stepState.stepId,
|
|
1743
|
+
duration
|
|
1744
|
+
)
|
|
1745
|
+
if (scheduled) {
|
|
1451
1746
|
throw new WorkflowAsyncException(runId, stepName)
|
|
1452
|
-
} else {
|
|
1453
|
-
// Inline mode - use setTimeout with actual duration
|
|
1454
|
-
await new Promise((resolve) =>
|
|
1455
|
-
setTimeout(resolve, getDurationInMilliseconds(duration))
|
|
1456
|
-
)
|
|
1457
|
-
await this.setStepResult(stepState.stepId, null)
|
|
1458
|
-
return
|
|
1459
1747
|
}
|
|
1748
|
+
|
|
1749
|
+
// Inline mode - use setTimeout with actual duration
|
|
1750
|
+
await new Promise((resolve) =>
|
|
1751
|
+
setTimeout(resolve, getDurationInMilliseconds(duration))
|
|
1752
|
+
)
|
|
1753
|
+
await this.setStepResult(stepState.stepId, null)
|
|
1460
1754
|
}
|
|
1461
1755
|
|
|
1462
1756
|
private getSuspendStepName(reason: string): string {
|
|
@@ -1592,12 +1886,17 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1592
1886
|
* Get the orchestrator queue name for a specific workflow.
|
|
1593
1887
|
* Checks queue meta for a per-workflow queue first (e.g. wf-orchestrator-{name}),
|
|
1594
1888
|
* falls back to the shared orchestrator queue.
|
|
1889
|
+
*
|
|
1890
|
+
* Reads from `queue.meta` (always populated globally) rather than
|
|
1891
|
+
* `queue.registrations` (only populated for queues this unit consumes).
|
|
1892
|
+
* In a per-unit deploy the orchestrator unit doesn't consume per-step
|
|
1893
|
+
* queues — but it produces to them — so registrations would miss them.
|
|
1595
1894
|
*/
|
|
1596
1895
|
protected getOrchestratorQueueName(workflowName?: string): string {
|
|
1597
1896
|
if (workflowName) {
|
|
1598
1897
|
const perWorkflow = `wf-orchestrator-${toKebab(workflowName)}`
|
|
1599
|
-
const
|
|
1600
|
-
if (
|
|
1898
|
+
const meta = pikkuState(null, 'queue', 'meta')
|
|
1899
|
+
if (meta[perWorkflow]) {
|
|
1601
1900
|
return perWorkflow
|
|
1602
1901
|
}
|
|
1603
1902
|
}
|
|
@@ -1607,8 +1906,8 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1607
1906
|
protected getStepWorkerQueueName(rpcName?: string): string {
|
|
1608
1907
|
if (rpcName) {
|
|
1609
1908
|
const perStep = `wf-step-${toKebab(rpcName)}`
|
|
1610
|
-
const
|
|
1611
|
-
if (
|
|
1909
|
+
const meta = pikkuState(null, 'queue', 'meta')
|
|
1910
|
+
if (meta[perStep]) {
|
|
1612
1911
|
return perStep
|
|
1613
1912
|
}
|
|
1614
1913
|
}
|