@pikku/core 0.12.79 → 0.12.82
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 +473 -0
- package/dist/dev/hot-reload.js +1 -6
- package/dist/ecosystem.d.ts +27 -0
- package/dist/ecosystem.js +26 -0
- package/dist/errors/error-handler.js +4 -2
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/function/function-runner.js +22 -47
- package/dist/function/functions.types.d.ts +9 -5
- package/dist/function/index.d.ts +1 -1
- package/dist/index.d.ts +14 -19
- package/dist/index.js +6 -11
- package/dist/middleware/auth-cookie.d.ts +0 -4
- package/dist/middleware/auth-cookie.js +38 -2
- package/dist/middleware/cors.js +1 -0
- package/dist/middleware/index.d.ts +1 -1
- package/dist/middleware/index.js +1 -1
- package/dist/middleware/remote-auth.js +14 -3
- package/dist/permissions.d.ts +8 -10
- package/dist/permissions.js +0 -11
- package/dist/pikku-state.js +16 -5
- package/dist/schema.js +35 -1
- package/dist/services/ai-agent-runner-service.d.ts +7 -0
- package/dist/services/ai-run-state-service.d.ts +17 -1
- package/dist/services/in-memory-ai-run-state-service.d.ts +6 -2
- package/dist/services/in-memory-ai-run-state-service.js +11 -1
- package/dist/services/in-memory-workflow-service.js +2 -0
- package/dist/services/index.d.ts +15 -15
- package/dist/services/index.js +5 -5
- package/dist/services/local-content-request-handler.d.ts +21 -0
- package/dist/services/local-content-request-handler.js +72 -53
- package/dist/services/local-content.d.ts +6 -0
- package/dist/services/local-content.js +14 -1
- package/dist/services/scoped-credential-service.d.ts +21 -0
- package/dist/services/scoped-credential-service.js +53 -0
- package/dist/services/workflow-service.d.ts +4 -2
- package/dist/testing/service-tests/agent-run-service-tests.d.ts +10 -0
- package/dist/testing/service-tests/agent-run-service-tests.js +72 -0
- package/dist/testing/service-tests/ai-storage-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/ai-storage-service-tests.js +302 -0
- package/dist/testing/service-tests/channel-store-tests.d.ts +3 -0
- package/dist/testing/service-tests/channel-store-tests.js +72 -0
- package/dist/testing/service-tests/credential-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/credential-service-tests.js +109 -0
- package/dist/testing/service-tests/deployment-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/deployment-service-tests.js +21 -0
- package/dist/testing/service-tests/event-hub-store-tests.d.ts +3 -0
- package/dist/testing/service-tests/event-hub-store-tests.js +34 -0
- package/dist/testing/service-tests/secret-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/secret-service-tests.js +80 -0
- package/dist/testing/service-tests/session-store-tests.d.ts +3 -0
- package/dist/testing/service-tests/session-store-tests.js +43 -0
- package/dist/testing/service-tests/workflow-run-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/workflow-run-service-tests.js +42 -0
- package/dist/testing/service-tests/workflow-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/workflow-service-tests.js +150 -0
- package/dist/testing/service-tests.d.ts +6 -0
- package/dist/testing/service-tests.js +26 -791
- package/dist/types/core.types.d.ts +11 -3
- package/dist/types/state.types.d.ts +14 -1
- package/dist/wirings/actor-flow/index.d.ts +1 -1
- package/dist/wirings/actor-flow/run-conversation.js +1 -6
- package/dist/wirings/ai-agent/agent-rpc.d.ts +15 -0
- package/dist/wirings/ai-agent/agent-rpc.js +53 -0
- package/dist/wirings/ai-agent/ai-agent-agui.js +1 -5
- package/dist/wirings/ai-agent/ai-agent-finalize.d.ts +58 -0
- package/dist/wirings/ai-agent/ai-agent-finalize.js +138 -0
- package/dist/wirings/ai-agent/ai-agent-interrupt.js +1 -0
- package/dist/wirings/ai-agent/ai-agent-memory.d.ts +2 -8
- package/dist/wirings/ai-agent/ai-agent-memory.js +37 -18
- package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +7 -0
- package/dist/wirings/ai-agent/ai-agent-model-config.js +44 -1
- package/dist/wirings/ai-agent/ai-agent-prepare.js +5 -9
- package/dist/wirings/ai-agent/ai-agent-runner.js +89 -147
- package/dist/wirings/ai-agent/ai-agent-stream.js +109 -97
- package/dist/wirings/ai-agent/ai-agent-turn.d.ts +57 -0
- package/dist/wirings/ai-agent/ai-agent-turn.js +82 -0
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +47 -2
- package/dist/wirings/ai-agent/index.d.ts +8 -7
- package/dist/wirings/ai-agent/index.js +5 -4
- package/dist/wirings/ai-agent/voice-input.js +1 -6
- package/dist/wirings/ai-agent/voice-output.js +2 -12
- package/dist/wirings/ai-scorer/ai-scorer-grade.d.ts +26 -0
- package/dist/wirings/ai-scorer/ai-scorer-grade.js +33 -0
- package/dist/wirings/ai-scorer/ai-scorer-judge.d.ts +17 -0
- package/dist/wirings/ai-scorer/ai-scorer-judge.js +92 -0
- package/dist/wirings/ai-scorer/ai-scorer-live.d.ts +15 -0
- package/dist/wirings/ai-scorer/ai-scorer-live.js +38 -0
- package/dist/wirings/ai-scorer/ai-scorer-registry.d.ts +18 -0
- package/dist/wirings/ai-scorer/ai-scorer-registry.js +46 -0
- package/dist/wirings/ai-scorer/ai-scorer-sampling.d.ts +8 -0
- package/dist/wirings/ai-scorer/ai-scorer-sampling.js +31 -0
- package/dist/wirings/ai-scorer/ai-scorer-snapshots.d.ts +10 -0
- package/dist/wirings/ai-scorer/ai-scorer-snapshots.js +40 -0
- package/dist/wirings/ai-scorer/ai-scorer-worker.d.ts +15 -0
- package/dist/wirings/ai-scorer/ai-scorer-worker.js +58 -0
- package/dist/wirings/ai-scorer/ai-scorer.d.ts +39 -0
- package/dist/wirings/ai-scorer/ai-scorer.js +40 -0
- package/dist/wirings/ai-scorer/ai-scorer.types.d.ts +90 -0
- package/dist/wirings/ai-scorer/ai-scorer.types.js +4 -0
- package/dist/wirings/ai-scorer/index.d.ts +6 -0
- package/dist/wirings/ai-scorer/index.js +5 -0
- package/dist/wirings/channel/channel-common.js +1 -0
- package/dist/wirings/channel/channel-handler.js +3 -5
- package/dist/wirings/channel/channel-rpc-service.d.ts +0 -6
- package/dist/wirings/channel/channel-rpc-service.js +0 -8
- package/dist/wirings/channel/channel-rpc.types.d.ts +6 -0
- package/dist/wirings/channel/channel-rpc.types.js +8 -0
- package/dist/wirings/channel/channel-runner.d.ts +1 -3
- package/dist/wirings/channel/channel-runner.js +16 -8
- package/dist/wirings/channel/channel.types.d.ts +2 -0
- package/dist/wirings/channel/index.d.ts +5 -6
- package/dist/wirings/channel/index.js +3 -4
- package/dist/wirings/channel/local/local-channel-runner.js +8 -1
- package/dist/wirings/channel/pikku-abstract-channel-handler.js +1 -0
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +3 -0
- package/dist/wirings/cli/channel/cli-channel-runner.js +2 -0
- package/dist/wirings/cli/channel/cli-raw-channel-runner.js +9 -1
- package/dist/wirings/cli/channel/index.d.ts +1 -2
- package/dist/wirings/cli/channel/index.js +0 -1
- package/dist/wirings/cli/cli-runner.js +17 -3
- package/dist/wirings/cli/cli.types.d.ts +0 -8
- package/dist/wirings/cli/command-parser.js +13 -0
- package/dist/wirings/credential/index.d.ts +1 -1
- package/dist/wirings/gateway/gateway-runner.js +9 -2
- package/dist/wirings/gateway/index.d.ts +1 -1
- package/dist/wirings/http/http-routes.js +2 -0
- package/dist/wirings/http/http-runner.d.ts +0 -10
- package/dist/wirings/http/http-runner.js +14 -15
- package/dist/wirings/http/http.types.d.ts +0 -10
- package/dist/wirings/http/index.d.ts +2 -3
- package/dist/wirings/http/index.js +1 -1
- package/dist/wirings/mcp/index.d.ts +1 -1
- package/dist/wirings/mcp/mcp-runner.d.ts +15 -7
- package/dist/wirings/mcp/mcp-runner.js +18 -11
- package/dist/wirings/persona/index.d.ts +3 -4
- package/dist/wirings/persona/index.js +2 -3
- package/dist/wirings/queue/index.d.ts +1 -3
- package/dist/wirings/queue/index.js +1 -3
- package/dist/wirings/rpc/addon-runner.d.ts +4 -0
- package/dist/wirings/rpc/addon-runner.js +19 -3
- package/dist/wirings/rpc/rpc-runner.d.ts +8 -51
- package/dist/wirings/rpc/rpc-runner.js +55 -86
- package/dist/wirings/rpc/rpc-types.d.ts +7 -0
- package/dist/wirings/rpc/wire-addon.d.ts +13 -0
- package/dist/wirings/rpc/wire-addon.js +4 -0
- package/dist/wirings/scheduler/index.d.ts +1 -1
- package/dist/wirings/secret/validate-secret-definitions.js +2 -2
- package/dist/wirings/trigger/index.d.ts +1 -1
- package/dist/wirings/trigger/pikku-trigger-service.d.ts +0 -4
- package/dist/wirings/trigger/trigger-runner.js +1 -0
- package/dist/wirings/virtual-user/index.d.ts +5 -6
- package/dist/wirings/virtual-user/index.js +2 -4
- package/dist/wirings/virtual-user/run-virtual-user.js +11 -11
- package/dist/wirings/virtual-user/virtual-user-derive.js +4 -25
- package/dist/wirings/virtual-user/virtual-user-dispositions.js +1 -4
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +100 -3
- package/dist/wirings/workflow/feature.js +7 -4
- package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -2
- package/dist/wirings/workflow/graph/graph-runner.js +8 -7
- package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +0 -4
- package/dist/wirings/workflow/index.d.ts +12 -7
- package/dist/wirings/workflow/index.js +7 -3
- package/dist/wirings/workflow/pikku-scenario-service.d.ts +17 -3
- package/dist/wirings/workflow/pikku-scenario-service.js +64 -10
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +28 -147
- package/dist/wirings/workflow/pikku-workflow-service.js +85 -493
- package/dist/wirings/workflow/scenario-step.types.d.ts +8 -0
- package/dist/wirings/workflow/workflow-approval-audit.d.ts +16 -0
- package/dist/wirings/workflow/workflow-approval-audit.js +40 -0
- package/dist/wirings/workflow/workflow-approval-policy.d.ts +20 -0
- package/dist/wirings/workflow/workflow-approval-policy.js +48 -0
- package/dist/wirings/workflow/workflow-approval.d.ts +67 -0
- package/dist/wirings/workflow/workflow-approval.js +177 -0
- package/dist/wirings/workflow/workflow-constants.d.ts +26 -0
- package/dist/wirings/workflow/workflow-constants.js +35 -0
- package/dist/wirings/workflow/workflow-errors.d.ts +58 -0
- package/dist/wirings/workflow/workflow-errors.js +112 -0
- package/dist/wirings/workflow/workflow-meta-resolver.d.ts +11 -0
- package/dist/wirings/workflow/workflow-meta-resolver.js +31 -0
- package/dist/wirings/workflow/workflow-queue-routing.d.ts +8 -0
- package/dist/wirings/workflow/workflow-queue-routing.js +38 -0
- package/dist/wirings/workflow/workflow-queue-wiring.d.ts +20 -0
- package/dist/wirings/workflow/workflow-queue-wiring.js +79 -0
- package/dist/wirings/workflow/workflow-recovery.d.ts +68 -0
- package/dist/wirings/workflow/workflow-recovery.js +101 -0
- package/dist/wirings/workflow/workflow-run-engine.types.d.ts +54 -0
- package/dist/wirings/workflow/workflow-run-ownership.d.ts +17 -0
- package/dist/wirings/workflow/workflow-run-ownership.js +30 -0
- package/dist/wirings/workflow/workflow-suspend.d.ts +12 -0
- package/dist/wirings/workflow/workflow-suspend.js +33 -0
- package/dist/wirings/workflow/workflow.types.d.ts +8 -1
- package/knowledge/decisions/internals/a-non-streaming-agent-run-registers-with-airunstate-too.md +22 -0
- package/knowledge/decisions/internals/a-resumed-agent-turn-is-as-interruptible-as-the-first.md +20 -0
- package/knowledge/decisions/internals/a-scenario-step-template-is-offered-unfilled.md +21 -0
- package/knowledge/decisions/internals/a-virtual-user-decides-whether-to-trust-memory-once-per-turn.md +21 -0
- package/knowledge/decisions/internals/a-wall-clock-threshold-is-a-load-test-in-disguise.md +46 -0
- package/knowledge/decisions/internals/a-workflow-wire-is-built-from-the-run-not-from-the-rpc-service.md +45 -0
- package/knowledge/decisions/internals/addon-pikku-meta-ships-at-the-package-root-or-under-dist.md +32 -0
- package/knowledge/decisions/internals/agent-context-waits-for-a-tool-result-still-being-written.md +21 -0
- package/knowledge/decisions/internals/agent-speech-travels-as-a-custom-agui-event.md +22 -0
- package/knowledge/decisions/internals/an-addon-scope-root-loses-to-a-root-the-host-already-declares.md +39 -0
- package/knowledge/decisions/internals/an-agent-interrupt-is-not-a-failure.md +23 -0
- package/knowledge/decisions/internals/an-agent-run-owned-by-another-instance-says-so.md +25 -0
- package/knowledge/decisions/internals/an-agent-stream-send-must-return-the-inner-sends-promise.md +22 -0
- package/knowledge/decisions/internals/an-empty-text-part-is-omitted-from-an-agent-message.md +20 -0
- package/knowledge/decisions/internals/an-empty-transcript-is-not-recorded.md +22 -0
- package/knowledge/decisions/internals/an-unref-d-timer-cannot-be-awaited-under-node-test.md +66 -0
- package/knowledge/decisions/internals/channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md +33 -0
- package/knowledge/decisions/internals/gateway-listener-middleware-runs-without-an-rpc-on-the-wire.md +43 -0
- package/knowledge/decisions/internals/hot-reload-writes-into-the-function-map-captured-at-startup.md +26 -0
- package/knowledge/decisions/internals/index.md +42 -3
- package/knowledge/decisions/internals/only-exposed-functions-enter-a-virtual-user-catalogue.md +21 -0
- package/knowledge/decisions/internals/scenario-given-and-when-are-sugar-but-then-is-not.md +24 -0
- package/knowledge/decisions/internals/side-effects-are-an-allowlist-not-a-boolean.md +34 -0
- package/knowledge/decisions/internals/speech-synthesis-picks-a-voice-per-sentence-and-warns-once.md +24 -0
- package/knowledge/decisions/internals/the-actor-prompt-says-json-because-of-json-object-mode.md +22 -0
- package/knowledge/decisions/internals/the-agent-done-event-goes-through-the-middleware-and-is-awaited.md +26 -0
- package/knowledge/decisions/internals/the-api-report-pins-members-not-just-names.md +43 -0
- package/knowledge/decisions/internals/the-ecosystem-entry-point-carries-the-adapter-surface.md +58 -0
- package/knowledge/decisions/internals/the-middleware-resolution-cache-is-deliberately-unbounded.md +40 -0
- package/knowledge/decisions/internals/the-per-invocation-rpc-view-is-a-class.md +40 -0
- package/knowledge/decisions/internals/the-persona-runtime-is-exported-from-the-persona-entry-point.md +28 -0
- package/knowledge/decisions/internals/the-transcript-event-is-sent-ahead-of-the-run.md +25 -0
- package/knowledge/decisions/internals/the-virtual-user-catalogue-is-the-only-gate-on-what-may-be-called.md +21 -0
- package/knowledge/decisions/internals/the-worker-disposition-is-the-one-that-is-not-testing.md +22 -0
- package/knowledge/decisions/internals/thread-history-records-the-transcript-not-the-audio.md +25 -0
- package/knowledge/decisions/internals/validate-runs-checks-by-precondition.md +115 -0
- package/knowledge/decisions/internals/virtual-user-step-order-comes-from-insertion-order.md +21 -0
- package/knowledge/decisions/internals/voice-output-speaks-unless-voice-input-explicitly-says-otherwise.md +24 -0
- package/knowledge/decisions/internals/wiring-registries-erase-the-generics-their-wire-functions-capture.md +35 -0
- package/knowledge/decisions/security/a-dropped-audit-write-is-always-logged.md +4 -2
- package/knowledge/decisions/security/a-function-never-receives-the-secret-service.md +37 -0
- package/knowledge/decisions/security/a-graph-run-starts-at-an-entry-node-the-graph-declared.md +33 -0
- package/knowledge/decisions/security/a-permission-gets-a-wire-it-cannot-reply-on.md +33 -0
- package/knowledge/decisions/security/a-step-runs-the-function-the-workflow-dispatched-it-with.md +37 -0
- package/knowledge/decisions/security/a-virtual-user-is-never-offered-a-step-that-would-forge-its-own-oracle.md +28 -0
- package/knowledge/decisions/security/a-workflow-run-is-read-and-approved-by-its-owner.md +50 -0
- package/knowledge/decisions/security/an-agent-approval-is-claimed-before-the-tool-runs.md +33 -0
- package/knowledge/decisions/security/an-approval-answer-outlives-the-run-it-answered.md +59 -0
- package/knowledge/decisions/security/an-upload-is-counted-as-it-arrives-not-buffered-then-measured.md +24 -0
- package/knowledge/decisions/security/index.md +9 -0
- package/knowledge/questions/channel-middleware-accepts-bare-factories-that-nothing-resolves.md +35 -0
- package/knowledge/questions/index.md +2 -1
- package/knowledge/questions/unauthorized-channel-replies-escape-the-declared-out-type.md +44 -0
- package/package.json +15 -3
- package/scripts/generate-api-report.d.mts +1 -0
- package/scripts/generate-api-report.mjs +89 -0
- package/scripts/generate-api-report.mts +343 -0
- package/src/api-report.test.ts +69 -0
- package/src/column-form.ts +1 -4
- package/src/crypto-utils.test.ts +15 -2
- package/src/dev/hot-reload.ts +2 -7
- package/src/ecosystem.ts +40 -0
- package/src/errors/error-handler.ts +5 -3
- package/src/errors/error.test.ts +4 -1
- package/src/errors/index.ts +1 -1
- package/src/function/function-runner.test.ts +53 -1
- package/src/function/function-runner.ts +33 -48
- package/src/function/functions.types.ts +15 -9
- package/src/function/index.ts +0 -2
- package/src/handle-error.ts +1 -1
- package/src/index.ts +3 -57
- package/src/middleware/auth-cookie.test.ts +50 -0
- package/src/middleware/auth-cookie.ts +38 -2
- package/src/middleware/cors.ts +2 -1
- package/src/middleware/index.ts +1 -1
- package/src/middleware/remote-auth.test.ts +43 -0
- package/src/middleware/remote-auth.ts +17 -3
- package/src/middleware-runner.ts +4 -2
- package/src/no-any-casts.test.ts +57 -0
- package/src/permissions.test.ts +3 -1
- package/src/permissions.ts +22 -24
- package/src/pikku-state.ts +22 -6
- package/src/public-surface.json +554 -0
- package/src/public-surface.json.README +25 -0
- package/src/public-surface.test.ts +105 -0
- package/src/removed-legacy-exports.test.ts +62 -0
- package/src/schema.test.ts +78 -0
- package/src/schema.ts +36 -1
- package/src/services/ai-agent-runner-service.ts +12 -1
- package/src/services/ai-run-state-service.ts +18 -1
- package/src/services/audit-service.ts +2 -2
- package/src/services/in-memory-ai-run-state-service.ts +16 -2
- package/src/services/in-memory-workflow-service.ts +2 -0
- package/src/services/index.ts +4 -47
- package/src/services/local-content-request-handler.test.ts +43 -5
- package/src/services/local-content-request-handler.ts +103 -74
- package/src/services/local-content.ts +15 -1
- package/src/services/local-email-service.ts +5 -1
- package/src/services/scoped-credential-service.test.ts +86 -0
- package/src/services/scoped-credential-service.ts +63 -0
- package/src/services/system-role-guard.test.ts +4 -1
- package/src/services/workflow-service.ts +9 -2
- package/src/side-effects-are-declared.test.ts +84 -0
- package/src/source-files-stay-composable.test.ts +41 -0
- package/src/testing/service-tests/agent-run-service-tests.ts +98 -0
- package/src/testing/service-tests/ai-storage-service-tests.ts +379 -0
- package/src/testing/service-tests/channel-store-tests.ts +98 -0
- package/src/testing/service-tests/credential-service-tests.ts +143 -0
- package/src/testing/service-tests/deployment-service-tests.ts +33 -0
- package/src/testing/service-tests/event-hub-store-tests.ts +48 -0
- package/src/testing/service-tests/secret-service-tests.ts +105 -0
- package/src/testing/service-tests/session-store-tests.ts +62 -0
- package/src/testing/service-tests/workflow-run-service-tests.ts +59 -0
- package/src/testing/service-tests/workflow-service-tests.ts +308 -0
- package/src/testing/service-tests.ts +31 -1111
- package/src/types/core.types.ts +14 -3
- package/src/types/state.types.ts +17 -1
- package/src/wirings/actor-flow/index.ts +0 -3
- package/src/wirings/actor-flow/run-conversation.ts +1 -6
- package/src/wirings/ai-agent/agent-rpc.ts +120 -0
- package/src/wirings/ai-agent/ai-agent-agui.ts +1 -5
- package/src/wirings/ai-agent/ai-agent-finalize.test.ts +186 -0
- package/src/wirings/ai-agent/ai-agent-finalize.ts +197 -0
- package/src/wirings/ai-agent/ai-agent-interrupt.test.ts +2 -1
- package/src/wirings/ai-agent/ai-agent-interrupt.ts +1 -0
- package/src/wirings/ai-agent/ai-agent-memory.ts +62 -39
- package/src/wirings/ai-agent/ai-agent-model-config.test.ts +72 -3
- package/src/wirings/ai-agent/ai-agent-model-config.ts +49 -1
- package/src/wirings/ai-agent/ai-agent-prepare.ts +12 -12
- package/src/wirings/ai-agent/ai-agent-resume-authorization.test.ts +1 -0
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +82 -2
- package/src/wirings/ai-agent/ai-agent-runner.ts +120 -160
- package/src/wirings/ai-agent/ai-agent-stream-output-hooks.test.ts +353 -0
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +2 -0
- package/src/wirings/ai-agent/ai-agent-stream.ts +152 -117
- package/src/wirings/ai-agent/ai-agent-thread-ownership.test.ts +1 -1
- package/src/wirings/ai-agent/ai-agent-turn.test.ts +67 -0
- package/src/wirings/ai-agent/ai-agent-turn.ts +122 -0
- package/src/wirings/ai-agent/ai-agent.types.ts +65 -5
- package/src/wirings/ai-agent/index.ts +2 -16
- package/src/wirings/ai-agent/voice-input.ts +1 -6
- package/src/wirings/ai-agent/voice-output.ts +2 -12
- package/src/wirings/ai-scorer/ai-scorer-grade.test.ts +106 -0
- package/src/wirings/ai-scorer/ai-scorer-grade.ts +55 -0
- package/src/wirings/ai-scorer/ai-scorer-judge.test.ts +143 -0
- package/src/wirings/ai-scorer/ai-scorer-judge.ts +120 -0
- package/src/wirings/ai-scorer/ai-scorer-live.test.ts +174 -0
- package/src/wirings/ai-scorer/ai-scorer-live.ts +56 -0
- package/src/wirings/ai-scorer/ai-scorer-registry.ts +63 -0
- package/src/wirings/ai-scorer/ai-scorer-sampling.test.ts +34 -0
- package/src/wirings/ai-scorer/ai-scorer-sampling.ts +36 -0
- package/src/wirings/ai-scorer/ai-scorer-snapshots.test.ts +49 -0
- package/src/wirings/ai-scorer/ai-scorer-snapshots.ts +46 -0
- package/src/wirings/ai-scorer/ai-scorer-worker.test.ts +122 -0
- package/src/wirings/ai-scorer/ai-scorer-worker.ts +69 -0
- package/src/wirings/ai-scorer/ai-scorer.ts +76 -0
- package/src/wirings/ai-scorer/ai-scorer.types.ts +107 -0
- package/src/wirings/ai-scorer/index.ts +24 -0
- package/src/wirings/channel/channel-common.ts +5 -2
- package/src/wirings/channel/channel-handler-shapes.test.ts +72 -0
- package/src/wirings/channel/channel-handler.ts +7 -7
- package/src/wirings/channel/channel-rpc-service.ts +0 -14
- package/src/wirings/channel/channel-rpc.test.ts +25 -4
- package/src/wirings/channel/channel-rpc.types.ts +14 -0
- package/src/wirings/channel/channel-runner.ts +33 -20
- package/src/wirings/channel/channel.types.ts +2 -0
- package/src/wirings/channel/index.ts +1 -20
- package/src/wirings/channel/local/local-channel-runner.test.ts +68 -0
- package/src/wirings/channel/local/local-channel-runner.ts +9 -2
- package/src/wirings/channel/pikku-abstract-channel-handler.ts +2 -1
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +6 -3
- package/src/wirings/cli/channel/cli-channel-runner.ts +3 -1
- package/src/wirings/cli/channel/cli-raw-channel-runner.test.ts +23 -0
- package/src/wirings/cli/channel/cli-raw-channel-runner.ts +12 -1
- package/src/wirings/cli/channel/cli-raw-client-runner.ts +4 -1
- package/src/wirings/cli/channel/index.ts +0 -7
- package/src/wirings/cli/cli-runner.test.ts +68 -0
- package/src/wirings/cli/cli-runner.ts +25 -5
- package/src/wirings/cli/cli.types.ts +0 -39
- package/src/wirings/cli/command-parser.test.ts +19 -0
- package/src/wirings/cli/command-parser.ts +17 -1
- package/src/wirings/credential/index.ts +0 -1
- package/src/wirings/gateway/gateway-channel-meta.test.ts +44 -0
- package/src/wirings/gateway/gateway-runner.ts +27 -19
- package/src/wirings/gateway/index.ts +0 -3
- package/src/wirings/http/http-routes.ts +4 -1
- package/src/wirings/http/http-runner.test.ts +66 -0
- package/src/wirings/http/http-runner.ts +21 -28
- package/src/wirings/http/http.types.ts +0 -15
- package/src/wirings/http/index.ts +2 -8
- package/src/wirings/http/pikku-fetch-http-request.ts +2 -2
- package/src/wirings/http/web-request.ts +1 -1
- package/src/wirings/mcp/index.ts +0 -1
- package/src/wirings/mcp/mcp-runner.test.ts +181 -0
- package/src/wirings/mcp/mcp-runner.ts +37 -21
- package/src/wirings/persona/index.ts +0 -8
- package/src/wirings/persona/persona-environments.test.ts +14 -3
- package/src/wirings/persona/persona.test.ts +13 -3
- package/src/wirings/persona/validate-personas.ts +5 -1
- package/src/wirings/queue/index.ts +0 -14
- package/src/wirings/queue/queue-runner.ts +1 -1
- package/src/wirings/rpc/addon-auth-tags.test.ts +1 -5
- package/src/wirings/rpc/addon-runner.ts +34 -3
- package/src/wirings/rpc/addon-secrets.test.ts +261 -0
- package/src/wirings/rpc/rpc-runner.test.ts +2 -0
- package/src/wirings/rpc/rpc-runner.ts +60 -136
- package/src/wirings/rpc/rpc-types.ts +11 -0
- package/src/wirings/rpc/wire-addon.ts +20 -1
- package/src/wirings/scheduler/index.ts +0 -1
- package/src/wirings/secret/validate-secret-definitions.test.ts +22 -0
- package/src/wirings/secret/validate-secret-definitions.ts +2 -2
- package/src/wirings/trigger/index.ts +0 -1
- package/src/wirings/trigger/pikku-trigger-service.ts +0 -5
- package/src/wirings/trigger/trigger-runner.ts +7 -5
- package/src/wirings/virtual-user/index.ts +0 -16
- package/src/wirings/virtual-user/run-virtual-user.test.ts +18 -9
- package/src/wirings/virtual-user/run-virtual-user.ts +28 -15
- package/src/wirings/virtual-user/virtual-user-agents.test.ts +4 -1
- package/src/wirings/virtual-user/virtual-user-derive.ts +4 -25
- package/src/wirings/virtual-user/virtual-user-dispositions.test.ts +7 -2
- package/src/wirings/virtual-user/virtual-user-dispositions.ts +1 -4
- package/src/wirings/virtual-user/virtual-user-intents.test.ts +13 -3
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +117 -4
- package/src/wirings/workflow/feature.ts +7 -4
- package/src/wirings/workflow/graph/graph-node.ts +1 -1
- package/src/wirings/workflow/graph/graph-runner.test.ts +76 -2
- package/src/wirings/workflow/graph/graph-runner.ts +13 -16
- package/src/wirings/workflow/graph/workflow-graph.types.ts +0 -5
- package/src/wirings/workflow/index.ts +14 -24
- package/src/wirings/workflow/pikku-scenario-service.ts +101 -27
- package/src/wirings/workflow/pikku-workflow-service.test.ts +15 -14
- package/src/wirings/workflow/pikku-workflow-service.ts +214 -743
- package/src/wirings/workflow/scenario-expectations.test.ts +75 -0
- package/src/wirings/workflow/scenario-hooks.test.ts +52 -0
- package/src/wirings/workflow/scenario-step.types.ts +8 -0
- package/src/wirings/workflow/workflow-approval-audit.ts +47 -0
- package/src/wirings/workflow/workflow-approval-policy.test.ts +524 -0
- package/src/wirings/workflow/workflow-approval-policy.ts +68 -0
- package/src/wirings/workflow/workflow-approval.ts +289 -0
- package/src/wirings/workflow/workflow-child-run-session.test.ts +89 -0
- package/src/wirings/workflow/workflow-constants.ts +47 -0
- package/src/wirings/workflow/workflow-dispatch-durability.test.ts +1 -1
- package/src/wirings/workflow/workflow-errors.ts +128 -0
- package/src/wirings/workflow/workflow-inline-authority.test.ts +12 -6
- package/src/wirings/workflow/workflow-meta-resolver.ts +45 -0
- package/src/wirings/workflow/workflow-missing-meta.test.ts +92 -0
- package/src/wirings/workflow/workflow-queue-routing.ts +85 -0
- package/src/wirings/workflow/workflow-queue-wiring.ts +149 -0
- package/src/wirings/workflow/workflow-recovery.ts +162 -0
- package/src/wirings/workflow/workflow-retry-policy.test.ts +1 -1
- package/src/wirings/workflow/workflow-run-authority.test.ts +212 -0
- package/src/wirings/workflow/workflow-run-engine.types.ts +91 -0
- package/src/wirings/workflow/workflow-run-ownership.ts +37 -0
- package/src/wirings/workflow/workflow-suspend.ts +61 -0
- package/src/wirings/workflow/workflow.types.ts +7 -9
- package/src/wirings-stay-decoupled.test.ts +127 -0
- package/tsconfig.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/internal.d.ts +0 -3
- package/dist/internal.js +0 -2
- package/dist/middleware/timeout.d.ts +0 -9
- package/dist/middleware/timeout.js +0 -15
- package/dist/pikku-response.d.ts +0 -6
- package/dist/pikku-response.js +0 -6
- package/dist/services/gopass-secrets.d.ts +0 -15
- package/dist/services/gopass-secrets.js +0 -76
- package/dist/services/http-scenario-actors.d.ts +0 -75
- package/dist/services/http-scenario-actors.js +0 -195
- package/dist/services/http-user-flow-actors.d.ts +0 -67
- package/dist/services/http-user-flow-actors.js +0 -193
- package/dist/services/scenario-actors-service.d.ts +0 -127
- package/dist/services/scenario-actors-service.js +0 -40
- package/dist/services/user-flow-actors-service.d.ts +0 -39
- package/dist/wirings/credential/wire-credential.d.ts +0 -48
- package/dist/wirings/credential/wire-credential.js +0 -47
- package/dist/wirings/oauth2/oauth2-client.d.ts +0 -47
- package/dist/wirings/oauth2/oauth2-client.js +0 -263
- package/dist/wirings/oauth2/oauth2-routes.d.ts +0 -35
- package/dist/wirings/oauth2/oauth2-routes.js +0 -146
- package/dist/wirings/scope/wire-scope.d.ts +0 -33
- package/dist/wirings/scope/wire-scope.js +0 -32
- package/dist/wirings/workflow/dsl/index.d.ts +0 -5
- package/dist/wirings/workflow/dsl/index.js +0 -4
- package/dist/wirings/workflow/graph/index.d.ts +0 -5
- package/dist/wirings/workflow/graph/index.js +0 -4
- package/src/internal.ts +0 -10
- /package/dist/{services/user-flow-actors-service.js → wirings/workflow/workflow-run-engine.types.js} +0 -0
|
@@ -1,168 +1,22 @@
|
|
|
1
|
-
import { runPikkuFunc
|
|
2
|
-
import { pikkuWorkflowWorkerFunc, pikkuWorkflowOrchestratorFunc, pikkuWorkflowSleeperFunc, } from './workflow-queue-workers.js';
|
|
3
|
-
import { wireQueueWorker } from '../queue/queue-runner.js';
|
|
1
|
+
import { runPikkuFunc } from '../../function/function-runner.js';
|
|
4
2
|
import { getSingletonServices, getCreateWireServices, pikkuState, } from '../../pikku-state.js';
|
|
5
3
|
import { getDurationInMilliseconds } from '../../time-utils.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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();
|
|
31
|
-
import { continueGraph, executeGraphStep, runWorkflowGraph, runFromMeta, } from './graph/graph-runner.js';
|
|
32
|
-
import { PikkuError, addError, isExpectedError, } from '../../errors/error-handler.js';
|
|
4
|
+
import { ChildWorkflowStartedException, continueGraph, executeGraphStep, runWorkflowGraph, runFromMeta, } from './graph/graph-runner.js';
|
|
5
|
+
import { isExpectedError } from '../../errors/error-handler.js';
|
|
6
|
+
import { PikkuMissingMetaError } from '../../errors/errors.js';
|
|
33
7
|
import { RPCNotFoundError } from '../rpc/rpc-runner.js';
|
|
34
|
-
import { ChildWorkflowStartedException } from './graph/graph-runner.js';
|
|
35
8
|
import { deriveInvocationId } from './workflow-invocation-id.js';
|
|
9
|
+
import { approvalDeciderFrom } from './workflow-approval-policy.js';
|
|
36
10
|
import { buildRunTimeline, reconstructStateAt, } from './run-timeline.js';
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
export class WorkflowCancelledException extends Error {
|
|
49
|
-
runId;
|
|
50
|
-
reason;
|
|
51
|
-
constructor(runId, reason) {
|
|
52
|
-
super(reason || 'Workflow cancelled');
|
|
53
|
-
this.runId = runId;
|
|
54
|
-
this.reason = reason;
|
|
55
|
-
this.name = 'WorkflowCancelledException';
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
export class WorkflowSuspendedException extends Error {
|
|
59
|
-
runId;
|
|
60
|
-
reason;
|
|
61
|
-
constructor(runId, reason) {
|
|
62
|
-
super(reason || 'Workflow suspended');
|
|
63
|
-
this.runId = runId;
|
|
64
|
-
this.reason = reason;
|
|
65
|
-
this.name = 'WorkflowSuspendedException';
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
export class WorkflowDispatchException extends Error {
|
|
69
|
-
runId;
|
|
70
|
-
stepName;
|
|
71
|
-
constructor(runId, stepName, options) {
|
|
72
|
-
super(`Failed to dispatch workflow step '${stepName}' (run ${runId})`, options);
|
|
73
|
-
this.runId = runId;
|
|
74
|
-
this.stepName = stepName;
|
|
75
|
-
this.name = 'WorkflowDispatchException';
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
export class WorkflowNotFoundError extends PikkuError {
|
|
79
|
-
constructor(name) {
|
|
80
|
-
super(`Workflow not found: ${name}`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
addError(WorkflowNotFoundError, {
|
|
84
|
-
status: 404,
|
|
85
|
-
message: 'Workflow not found.',
|
|
86
|
-
});
|
|
87
|
-
export class WorkflowRunNotFoundError extends PikkuError {
|
|
88
|
-
constructor(runId) {
|
|
89
|
-
super(`Workflow run not found: ${runId}`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
addError(WorkflowRunNotFoundError, {
|
|
93
|
-
status: 404,
|
|
94
|
-
message: 'Workflow run not found.',
|
|
95
|
-
});
|
|
96
|
-
export class WorkflowRunFailedError extends PikkuError {
|
|
97
|
-
payload;
|
|
98
|
-
constructor(message) {
|
|
99
|
-
super(`Workflow run failed: ${message ?? 'unknown'}`);
|
|
100
|
-
this.payload = { message };
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
addError(WorkflowRunFailedError, {
|
|
104
|
-
status: 422,
|
|
105
|
-
message: 'Workflow run failed.',
|
|
106
|
-
});
|
|
107
|
-
export class WorkflowRunCancelledError extends PikkuError {
|
|
108
|
-
constructor() {
|
|
109
|
-
super('Workflow was cancelled');
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
addError(WorkflowRunCancelledError, {
|
|
113
|
-
status: 409,
|
|
114
|
-
message: 'Workflow was cancelled.',
|
|
115
|
-
});
|
|
116
|
-
export class WorkflowApprovalResolvedError extends PikkuError {
|
|
117
|
-
payload;
|
|
118
|
-
constructor(reason, outcome) {
|
|
119
|
-
super(`Approval already ${outcome}: ${reason}`);
|
|
120
|
-
this.payload = { reason, outcome };
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
addError(WorkflowApprovalResolvedError, {
|
|
124
|
-
status: 409,
|
|
125
|
-
message: 'Approval has already been resolved.',
|
|
126
|
-
});
|
|
127
|
-
export class WorkflowServiceNotInitialized extends Error {
|
|
128
|
-
}
|
|
129
|
-
export class WorkflowStepNameNotString extends Error {
|
|
130
|
-
constructor(stepName) {
|
|
131
|
-
super(`Workflow step name must be a string. Received: ${typeof stepName}`);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
const WORKFLOW_END_STATES = new Set([
|
|
135
|
-
'completed',
|
|
136
|
-
'failed',
|
|
137
|
-
'cancelled',
|
|
138
|
-
'suspended',
|
|
139
|
-
]);
|
|
140
|
-
const WORKFLOW_TERMINAL_STATES = new Set([
|
|
141
|
-
'completed',
|
|
142
|
-
'failed',
|
|
143
|
-
'cancelled',
|
|
144
|
-
]);
|
|
145
|
-
/** Idle window before a `running` run with nothing in flight is treated as stalled. */
|
|
146
|
-
const DEFAULT_STALLED_RUN_MS = 5 * 60_000;
|
|
147
|
-
/** Runs re-driven per `recoverStalledRuns` call, so one sweep is bounded. */
|
|
148
|
-
const DEFAULT_STALLED_RUN_LIMIT = 100;
|
|
149
|
-
/**
|
|
150
|
-
* How long a step may sit `pending` before the relay assumes its dispatch was
|
|
151
|
-
* lost. This is a bet that no healthy queue takes this long to move a job from
|
|
152
|
-
* `pending` to `running`; set it above the observed p99 of that latency.
|
|
153
|
-
*/
|
|
154
|
-
const DEFAULT_UNDISPATCHED_STEP_MS = 30_000;
|
|
155
|
-
/** Steps re-driven per `relayUndispatchedSteps` call, so one tick is bounded. */
|
|
156
|
-
const DEFAULT_UNDISPATCHED_STEP_LIMIT = 100;
|
|
157
|
-
/** First wait before a step already re-dispatched once is re-dispatched again. */
|
|
158
|
-
const REDISPATCH_BACKOFF_MS = 30_000;
|
|
159
|
-
/** Ceiling on the doubling backoff, so a permanently stuck step still gets swept. */
|
|
160
|
-
const REDISPATCH_BACKOFF_MAX_MS = 10 * 60_000;
|
|
161
|
-
/** Bound on the in-process backoff map, so a long-lived process cannot grow it without bound. */
|
|
162
|
-
const REDISPATCH_BACKOFF_MAX_ENTRIES = 10_000;
|
|
163
|
-
const WORKFLOW_POLL_MIN_MS = 10;
|
|
164
|
-
const WORKFLOW_POLL_FACTOR = 1.6;
|
|
165
|
-
const WORKFLOW_CHILD_POLL_MAX_MS = 500;
|
|
11
|
+
import { DEFAULT_STEP_RETRIES, WORKFLOW_CHILD_POLL_MAX_MS, WORKFLOW_END_STATES, WORKFLOW_POLL_FACTOR, WORKFLOW_POLL_MIN_MS, WORKFLOW_TERMINAL_STATES, } from './workflow-constants.js';
|
|
12
|
+
import { WorkflowAsyncException, WorkflowCancelledException, WorkflowDispatchException, WorkflowNotFoundError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotFoundError, WorkflowStepFunctionMismatchError, WorkflowStepNameNotString, WorkflowSuspendedException, } from './workflow-errors.js';
|
|
13
|
+
import { resolveWorkflowMeta } from './workflow-meta-resolver.js';
|
|
14
|
+
import { jobGroupFor, orchestratorQueueName, resolveWorkflowConfig, stepJobOptions, stepWorkerQueueName, } from './workflow-queue-routing.js';
|
|
15
|
+
import { wireWorkflowQueueWorkers } from './workflow-queue-wiring.js';
|
|
16
|
+
import { approvalStepNameFor, evaluateApprovalStep, recordApprovalDecision, } from './workflow-approval.js';
|
|
17
|
+
import { auditApprovalDecision } from './workflow-approval-audit.js';
|
|
18
|
+
import { recordSuspension, suspendStepNameFor } from './workflow-suspend.js';
|
|
19
|
+
import { RedispatchBackoff, sweepStalledRuns, sweepUndispatchedSteps, } from './workflow-recovery.js';
|
|
166
20
|
export class PikkuWorkflowService {
|
|
167
21
|
runExtension;
|
|
168
22
|
runContexts = new Map();
|
|
@@ -236,76 +90,12 @@ export class PikkuWorkflowService {
|
|
|
236
90
|
return written;
|
|
237
91
|
}
|
|
238
92
|
wireQueueWorkers() {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
functionType: 'helper',
|
|
245
|
-
inputSchemaName: null,
|
|
246
|
-
outputSchemaName: null,
|
|
93
|
+
wireWorkflowQueueWorkers({
|
|
94
|
+
strategy: this.queueStrategy,
|
|
95
|
+
concurrency: this.queueConcurrency,
|
|
96
|
+
groupConcurrency: this.queueGroupConcurrency,
|
|
97
|
+
getLogger: () => this.logger,
|
|
247
98
|
});
|
|
248
|
-
const queueMeta = pikkuState(null, 'queue', 'meta');
|
|
249
|
-
const registerWorkflowFunc = (funcId, func, queueName, config) => {
|
|
250
|
-
if (functions.has(funcId))
|
|
251
|
-
return;
|
|
252
|
-
addFunction(funcId, func);
|
|
253
|
-
if (!queueMeta[queueName]) {
|
|
254
|
-
queueMeta[queueName] = { pikkuFuncId: funcId, name: queueName };
|
|
255
|
-
}
|
|
256
|
-
wireQueueWorker({ name: queueName, func, config });
|
|
257
|
-
if (!functionsMeta[funcId]) {
|
|
258
|
-
functionsMeta[funcId] = mkMeta(funcId);
|
|
259
|
-
}
|
|
260
|
-
};
|
|
261
|
-
const sharedGroups = this.queueStrategy === 'shared-groups';
|
|
262
|
-
const sharedQueueConfig = sharedGroups
|
|
263
|
-
? {
|
|
264
|
-
batchSize: this.queueConcurrency,
|
|
265
|
-
groupConcurrency: this.queueGroupConcurrency,
|
|
266
|
-
}
|
|
267
|
-
: undefined;
|
|
268
|
-
registerWorkflowFunc('pikkuWorkflowOrchestrator', { func: pikkuWorkflowOrchestratorFunc }, 'pikku-workflow-orchestrator', sharedQueueConfig);
|
|
269
|
-
registerWorkflowFunc('pikkuWorkflowStepWorker', { func: pikkuWorkflowWorkerFunc }, 'pikku-workflow-step-worker', sharedQueueConfig);
|
|
270
|
-
const registerQueueWorkers = (queueMeta) => {
|
|
271
|
-
for (const [queueName, meta] of Object.entries(queueMeta)) {
|
|
272
|
-
if (functions.has(meta.pikkuFuncId))
|
|
273
|
-
continue;
|
|
274
|
-
if (queueName.startsWith('wf-orchestrator-')) {
|
|
275
|
-
registerWorkflowFunc(meta.pikkuFuncId, { func: pikkuWorkflowOrchestratorFunc }, queueName);
|
|
276
|
-
}
|
|
277
|
-
else if (queueName.startsWith('wf-step-')) {
|
|
278
|
-
registerWorkflowFunc(meta.pikkuFuncId, { func: pikkuWorkflowWorkerFunc }, queueName);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
};
|
|
282
|
-
if (!sharedGroups) {
|
|
283
|
-
registerQueueWorkers(pikkuState(null, 'queue', 'meta'));
|
|
284
|
-
const addons = pikkuState(null, 'addons', 'packages');
|
|
285
|
-
if (addons) {
|
|
286
|
-
for (const [, addon] of addons) {
|
|
287
|
-
const addonQueueMeta = pikkuState(addon.package, 'queue', 'meta');
|
|
288
|
-
if (addonQueueMeta) {
|
|
289
|
-
registerQueueWorkers(addonQueueMeta);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
const workflowCount = Object.keys(pikkuState(null, 'workflows', 'meta') ?? {}).length;
|
|
295
|
-
const perWorkflowQueues = Object.keys(queueMeta).filter((name) => name.startsWith('wf-orchestrator-')).length;
|
|
296
|
-
if (workflowCount > 0 && perWorkflowQueues === 0) {
|
|
297
|
-
this.logger?.warn?.(`[pikku] ${workflowCount} workflow(s) registered but no per-workflow orchestrator queues were found in queue meta. ` +
|
|
298
|
-
`All workflows will share a single orchestrator queue, where one slow step blocks every other workflow behind it. ` +
|
|
299
|
-
`Check that the generated bootstrap imports the queue-workers meta (pikku-queue-workers-wirings-meta.gen.js).`);
|
|
300
|
-
}
|
|
301
|
-
if (!functions.has('pikkuWorkflowSleeper')) {
|
|
302
|
-
addFunction('pikkuWorkflowSleeper', {
|
|
303
|
-
func: pikkuWorkflowSleeperFunc,
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
if (!functionsMeta.pikkuWorkflowSleeper) {
|
|
307
|
-
functionsMeta.pikkuWorkflowSleeper = mkMeta('pikkuWorkflowSleeper');
|
|
308
|
-
}
|
|
309
99
|
}
|
|
310
100
|
async isInline(runId) {
|
|
311
101
|
const context = this.runContexts.get(runId);
|
|
@@ -465,41 +255,6 @@ export class PikkuWorkflowService {
|
|
|
465
255
|
async findStalledRunIds(_before, _limit) {
|
|
466
256
|
return [];
|
|
467
257
|
}
|
|
468
|
-
/**
|
|
469
|
-
* Re-drive runs whose next move was lost, and report which were resumed.
|
|
470
|
-
*
|
|
471
|
-
* Arming a step is two writes to two systems — the step row, then the queue
|
|
472
|
-
* or scheduler job — so a process that dies between them leaves a run that is
|
|
473
|
-
* `running` with nothing in flight. Nothing notices: the run parks on a step
|
|
474
|
-
* that will never complete and never error, so it neither finishes nor fails.
|
|
475
|
-
* (Seen on a `workflow.sleep()`: a deploy restart landed between the sleep
|
|
476
|
-
* step's insert and its timer, parking the run permanently.)
|
|
477
|
-
*
|
|
478
|
-
* Replay is the recovery — `resumeWorkflow` re-orchestrates from persisted
|
|
479
|
-
* step state, and every settled step is memoized, so resuming a run that was
|
|
480
|
-
* not actually stuck costs an orchestration pass and changes nothing. That
|
|
481
|
-
* idempotence is what makes an idle-time heuristic safe here; a run that is
|
|
482
|
-
* legitimately mid-sleep is excluded anyway, since its step is `scheduled`.
|
|
483
|
-
*
|
|
484
|
-
* This is not self-starting. Call it from a scheduled task at whatever
|
|
485
|
-
* interval suits the workload.
|
|
486
|
-
*/
|
|
487
|
-
async recoverStalledRuns(options) {
|
|
488
|
-
const before = new Date(Date.now() - (options?.stalledAfterMs ?? DEFAULT_STALLED_RUN_MS));
|
|
489
|
-
const runIds = await this.findStalledRunIds(before, options?.limit ?? DEFAULT_STALLED_RUN_LIMIT);
|
|
490
|
-
const resumed = [];
|
|
491
|
-
for (const runId of runIds) {
|
|
492
|
-
try {
|
|
493
|
-
await this.resumeWorkflow(runId);
|
|
494
|
-
resumed.push(runId);
|
|
495
|
-
}
|
|
496
|
-
catch (err) {
|
|
497
|
-
// One unresumable run must not stop the sweep from recovering the rest.
|
|
498
|
-
getSingletonServices()?.logger?.error(`Failed to resume stalled workflow run ${runId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
return { resumed };
|
|
502
|
-
}
|
|
503
258
|
/**
|
|
504
259
|
* Runs holding a step that has sat `pending` since before `before`, paired
|
|
505
260
|
* with the step that flagged them.
|
|
@@ -516,89 +271,30 @@ export class PikkuWorkflowService {
|
|
|
516
271
|
async findUndispatchedSteps(_before, _limit) {
|
|
517
272
|
return [];
|
|
518
273
|
}
|
|
274
|
+
redispatchBackoff = new RedispatchBackoff();
|
|
275
|
+
get sweepDeps() {
|
|
276
|
+
return {
|
|
277
|
+
resume: (runId) => this.resumeWorkflow(runId),
|
|
278
|
+
logger: getSingletonServices()?.logger,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
519
281
|
/**
|
|
520
|
-
* Re-drive
|
|
521
|
-
*
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
*
|
|
528
|
-
*
|
|
529
|
-
*
|
|
530
|
-
* indistinguishable from one whose job is merely still queued — so a step
|
|
531
|
-
* past `undispatchedAfterMs` is re-dispatched regardless, and correctness
|
|
532
|
-
* rests on the claim rather than on the guess being right. A redundant
|
|
533
|
-
* dispatch costs one queue message: the loser reads `running` and returns
|
|
534
|
-
* without invoking anything.
|
|
535
|
-
*
|
|
536
|
-
* Re-dispatches back off per step (doubling from 30s, capped at 10m) so a
|
|
537
|
-
* genuine queue backlog is not amplified by a tick that keeps firing at the
|
|
538
|
-
* steps the backlog is already delaying. The backoff is per process and
|
|
539
|
-
* advisory — losing it on restart costs extra dispatches, never correctness.
|
|
540
|
-
*
|
|
541
|
-
* This is not self-starting. Call it from a scheduled task; ~30s suits a
|
|
542
|
-
* queue whose `pending`→`running` latency is well under that.
|
|
282
|
+
* Re-drive runs whose next move was lost. Not self-starting — call it from a
|
|
283
|
+
* scheduled task at whatever interval suits the workload.
|
|
284
|
+
*/
|
|
285
|
+
async recoverStalledRuns(options) {
|
|
286
|
+
return sweepStalledRuns((before, limit) => this.findStalledRunIds(before, limit), options, this.sweepDeps);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Re-drive steps whose dispatch was lost. Not self-starting — call it from a
|
|
290
|
+
* scheduled task; ~30s suits a queue whose `pending`→`running` latency is
|
|
291
|
+
* well under that.
|
|
543
292
|
*/
|
|
544
293
|
async relayUndispatchedSteps(options) {
|
|
545
|
-
|
|
546
|
-
(options?.undispatchedAfterMs ?? DEFAULT_UNDISPATCHED_STEP_MS));
|
|
547
|
-
const steps = await this.findUndispatchedSteps(before, options?.limit ?? DEFAULT_UNDISPATCHED_STEP_LIMIT);
|
|
548
|
-
// Backoff is keyed by run, not step, because the run is the unit of
|
|
549
|
-
// re-drive: `resumeWorkflow` replays the whole run and re-dispatches every
|
|
550
|
-
// step still owed a job. Holding off a single step while resuming its run
|
|
551
|
-
// would not suppress anything.
|
|
552
|
-
const now = Date.now();
|
|
553
|
-
const runIds = new Set();
|
|
554
|
-
for (const { runId } of steps) {
|
|
555
|
-
const eligibleAt = this.redispatchBackoff.get(runId);
|
|
556
|
-
if (eligibleAt !== undefined && eligibleAt > now) {
|
|
557
|
-
continue;
|
|
558
|
-
}
|
|
559
|
-
this.noteRedispatch(runId, now);
|
|
560
|
-
runIds.add(runId);
|
|
561
|
-
}
|
|
562
|
-
const redispatched = [];
|
|
563
|
-
for (const runId of runIds) {
|
|
564
|
-
try {
|
|
565
|
-
await this.resumeWorkflow(runId);
|
|
566
|
-
redispatched.push(runId);
|
|
567
|
-
}
|
|
568
|
-
catch (err) {
|
|
569
|
-
// One unresumable run must not stop the tick from relaying the rest.
|
|
570
|
-
getSingletonServices()?.logger?.error(`Failed to re-dispatch workflow run ${runId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
return { redispatched };
|
|
574
|
-
}
|
|
575
|
-
/** Advisory, per-process record of when a run may next be re-dispatched. */
|
|
576
|
-
redispatchBackoff = new Map();
|
|
577
|
-
redispatchDelays = new Map();
|
|
578
|
-
noteRedispatch(runId, now) {
|
|
579
|
-
const previous = this.redispatchDelays.get(runId);
|
|
580
|
-
const delay = Math.min(previous === undefined ? REDISPATCH_BACKOFF_MS : previous * 2, REDISPATCH_BACKOFF_MAX_MS);
|
|
581
|
-
// A run that settles is never returned again, so entries are only evicted by
|
|
582
|
-
// this bound — oldest first, which is also least recently re-dispatched.
|
|
583
|
-
if (this.redispatchBackoff.size >= REDISPATCH_BACKOFF_MAX_ENTRIES) {
|
|
584
|
-
const oldest = this.redispatchBackoff.keys().next();
|
|
585
|
-
if (!oldest.done) {
|
|
586
|
-
this.redispatchBackoff.delete(oldest.value);
|
|
587
|
-
this.redispatchDelays.delete(oldest.value);
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
this.redispatchDelays.set(runId, delay);
|
|
591
|
-
this.redispatchBackoff.set(runId, now + delay);
|
|
294
|
+
return sweepUndispatchedSteps((before, limit) => this.findUndispatchedSteps(before, limit), this.redispatchBackoff, options, this.sweepDeps);
|
|
592
295
|
}
|
|
593
296
|
resolveStepJobOptions(stepOptions) {
|
|
594
|
-
|
|
595
|
-
const retryDelay = stepOptions?.retryDelay;
|
|
596
|
-
const backoff = retryDelay !== undefined && retryDelay !== 'exponential'
|
|
597
|
-
? { type: 'fixed', delay: getDurationInMilliseconds(retryDelay) }
|
|
598
|
-
: retries > 0 || retryDelay === 'exponential'
|
|
599
|
-
? 'exponential'
|
|
600
|
-
: undefined;
|
|
601
|
-
return { attempts: retries + 1, ...(backoff ? { backoff } : {}) };
|
|
297
|
+
return stepJobOptions(stepOptions);
|
|
602
298
|
}
|
|
603
299
|
async queueStepWorker(runId, stepName, rpcName, data, stepOptions, fromStepName) {
|
|
604
300
|
const queueService = this.verifyQueueService();
|
|
@@ -676,6 +372,18 @@ export class PikkuWorkflowService {
|
|
|
676
372
|
throw new WorkflowNotFoundError(name);
|
|
677
373
|
}
|
|
678
374
|
if (workflowMeta.source === 'graph') {
|
|
375
|
+
// A caller-supplied startNode must be one of the graph's declared entry
|
|
376
|
+
// nodes. startWorkflow is the boundary the public
|
|
377
|
+
// `/workflow/:name/graph/:nodeId` route and triggers enter through, so
|
|
378
|
+
// without this a request could name any dependency-free node — one whose
|
|
379
|
+
// input reads only `trigger` — and fire its RPC directly with
|
|
380
|
+
// attacker-chosen data, skipping every upstream eligibility, validation or
|
|
381
|
+
// approval node. (Internal resume/replay drives runWorkflowGraph directly
|
|
382
|
+
// and is unaffected.)
|
|
383
|
+
if (options?.startNode &&
|
|
384
|
+
!(workflowMeta.entryNodeIds ?? []).includes(options.startNode)) {
|
|
385
|
+
throw new Error(`Workflow graph '${name}': '${options.startNode}' is not a declared entry node`);
|
|
386
|
+
}
|
|
679
387
|
const shouldInline = options?.inline || !getSingletonServices()?.queueService;
|
|
680
388
|
return runWorkflowGraph(this, name, input, rpcService, shouldInline, options?.startNode, wire, workflowMeta);
|
|
681
389
|
}
|
|
@@ -859,6 +567,9 @@ export class PikkuWorkflowService {
|
|
|
859
567
|
return;
|
|
860
568
|
}
|
|
861
569
|
const registrations = pikkuState(pkgName, 'workflows', 'registrations');
|
|
570
|
+
if (!workflowMeta) {
|
|
571
|
+
throw new PikkuMissingMetaError(`Missing generated metadata for workflow '${run.workflow}'`);
|
|
572
|
+
}
|
|
862
573
|
const workflow = registrations.get(resolved?.resolvedName ?? run.workflow);
|
|
863
574
|
if (!workflow) {
|
|
864
575
|
throw new WorkflowNotFoundError(run.workflow);
|
|
@@ -869,11 +580,10 @@ export class PikkuWorkflowService {
|
|
|
869
580
|
: null;
|
|
870
581
|
const workflowWire = this.createWorkflowWire(run.workflow, runId, rpcService, addonNs);
|
|
871
582
|
workflowWire.pikkuUserId = run.wire?.pikkuUserId;
|
|
583
|
+
// No `rpc` yet — runPikkuFunc attaches it lazily for the invocation.
|
|
872
584
|
const wire = {
|
|
873
585
|
workflow: workflowWire,
|
|
874
586
|
pikkuUserId: run.wire?.pikkuUserId,
|
|
875
|
-
session: rpcService?.wire?.session,
|
|
876
|
-
rpc: rpcService?.wire?.rpc,
|
|
877
587
|
};
|
|
878
588
|
this.runExtension?.decorateRunWire(wire, {
|
|
879
589
|
runId,
|
|
@@ -990,6 +700,11 @@ export class PikkuWorkflowService {
|
|
|
990
700
|
async executeWorkflowStepInner(runId, stepName, rpcName, data, rpcService) {
|
|
991
701
|
const claimed = await this.withStepLock(runId, stepName, async () => {
|
|
992
702
|
const stepState = await this.getStepState(runId, stepName);
|
|
703
|
+
// knowledge: decisions/security/a-step-runs-the-function-the-workflow-dispatched-it-with.md
|
|
704
|
+
if (stepState.rpcName !== undefined &&
|
|
705
|
+
stepState.rpcName !== (rpcName ?? null)) {
|
|
706
|
+
throw new WorkflowStepFunctionMismatchError(runId, stepName);
|
|
707
|
+
}
|
|
993
708
|
if (stepState.status === 'succeeded' || stepState.status === 'running') {
|
|
994
709
|
return null;
|
|
995
710
|
}
|
|
@@ -1027,7 +742,7 @@ export class PikkuWorkflowService {
|
|
|
1027
742
|
}
|
|
1028
743
|
}
|
|
1029
744
|
if (graphNodeId) {
|
|
1030
|
-
result = await executeGraphStep(this, rpcService, runId, stepState.stepId, graphNodeId, rpcName, data, run.workflow);
|
|
745
|
+
result = await executeGraphStep(this, rpcService, runId, stepState.stepId, graphNodeId, workflowMeta.nodes[graphNodeId].rpcName, data, run.workflow);
|
|
1031
746
|
}
|
|
1032
747
|
else {
|
|
1033
748
|
const subWorkflowMeta = meta[rpcName];
|
|
@@ -1037,7 +752,7 @@ export class PikkuWorkflowService {
|
|
|
1037
752
|
id: rpcName,
|
|
1038
753
|
parentRunId: runId,
|
|
1039
754
|
parentStepId: stepState.stepId,
|
|
1040
|
-
pikkuUserId:
|
|
755
|
+
pikkuUserId: run.wire?.pikkuUserId,
|
|
1041
756
|
};
|
|
1042
757
|
const shouldInline = !getSingletonServices()?.queueService;
|
|
1043
758
|
const { runId: childRunId } = await this.startWorkflow(rpcName, data, childWire, rpcService, { inline: shouldInline });
|
|
@@ -1202,7 +917,7 @@ export class PikkuWorkflowService {
|
|
|
1202
917
|
type: 'workflow',
|
|
1203
918
|
id: rpcName,
|
|
1204
919
|
parentRunId: runId,
|
|
1205
|
-
pikkuUserId:
|
|
920
|
+
pikkuUserId: (await this.getRunIdentity(runId))?.wire?.pikkuUserId,
|
|
1206
921
|
};
|
|
1207
922
|
const { runId: childRunId } = await this.startWorkflow(rpcName, data, childWire, rpcService, { inline: true });
|
|
1208
923
|
await this.setStepChildRunId(currentStepState.stepId, childRunId);
|
|
@@ -1281,39 +996,10 @@ export class PikkuWorkflowService {
|
|
|
1281
996
|
await new Promise((resolve) => setTimeout(resolve, getDurationInMilliseconds(duration)));
|
|
1282
997
|
await this.setStepResult(stepState.stepId, null);
|
|
1283
998
|
}
|
|
1284
|
-
getSuspendStepName(reason) {
|
|
1285
|
-
return `__workflow_suspend:${reason}`;
|
|
1286
|
-
}
|
|
1287
999
|
async suspendStep(runId, reason) {
|
|
1288
1000
|
const fromStepName = this.lastStepName(runId);
|
|
1289
|
-
const suspendStepName = this.nextStepKey(runId,
|
|
1290
|
-
await this.withStepLock(runId, suspendStepName,
|
|
1291
|
-
let stepState;
|
|
1292
|
-
try {
|
|
1293
|
-
stepState = await this.getStepState(runId, suspendStepName);
|
|
1294
|
-
}
|
|
1295
|
-
catch {
|
|
1296
|
-
stepState = await this.insertStepState(runId, suspendStepName, 'pikkuWorkflowSuspend', {
|
|
1297
|
-
reason,
|
|
1298
|
-
}, undefined, fromStepName);
|
|
1299
|
-
}
|
|
1300
|
-
if (!stepState.stepId) {
|
|
1301
|
-
stepState = await this.insertStepState(runId, suspendStepName, 'pikkuWorkflowSuspend', {
|
|
1302
|
-
reason,
|
|
1303
|
-
}, undefined, fromStepName);
|
|
1304
|
-
}
|
|
1305
|
-
if (stepState.status === 'succeeded') {
|
|
1306
|
-
return;
|
|
1307
|
-
}
|
|
1308
|
-
if (stepState.status === 'pending') {
|
|
1309
|
-
await this.setStepRunning(stepState.stepId);
|
|
1310
|
-
}
|
|
1311
|
-
await this.setStepResult(stepState.stepId, {
|
|
1312
|
-
reason,
|
|
1313
|
-
suspendedAt: new Date().toISOString(),
|
|
1314
|
-
});
|
|
1315
|
-
throw new WorkflowSuspendedException(runId, reason);
|
|
1316
|
-
});
|
|
1001
|
+
const suspendStepName = this.nextStepKey(runId, suspendStepNameFor(reason));
|
|
1002
|
+
await this.withStepLock(runId, suspendStepName, () => recordSuspension(this.approvalStore, runId, reason, suspendStepName, fromStepName));
|
|
1317
1003
|
}
|
|
1318
1004
|
async scheduleRunWake(runId, delay) {
|
|
1319
1005
|
try {
|
|
@@ -1331,96 +1017,27 @@ export class PikkuWorkflowService {
|
|
|
1331
1017
|
this.logger?.warn(`Failed to schedule approval expiry wake for run ${runId}; expiry will still resolve on the next replay`, error);
|
|
1332
1018
|
}
|
|
1333
1019
|
}
|
|
1334
|
-
|
|
1335
|
-
return
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1020
|
+
get approvalStore() {
|
|
1021
|
+
return {
|
|
1022
|
+
getStepState: (runId, stepName) => this.getStepState(runId, stepName),
|
|
1023
|
+
insertStepState: (runId, stepName, rpcName, input, output, fromStepName) => this.insertStepState(runId, stepName, rpcName, input, output, fromStepName),
|
|
1024
|
+
setStepRunning: (stepId) => this.setStepRunning(stepId),
|
|
1025
|
+
setStepResult: (stepId, result) => this.setStepResult(stepId, result),
|
|
1026
|
+
getRunState: (runId) => this.getRunState(runId),
|
|
1027
|
+
updateRunState: (runId, key, value) => this.updateRunState(runId, key, value),
|
|
1028
|
+
resumeWorkflow: (runId) => this.resumeWorkflow(runId),
|
|
1029
|
+
scheduleRunWake: (runId, delay) => this.scheduleRunWake(runId, delay),
|
|
1030
|
+
getRunOwner: async (runId) => (await this.getRunIdentity(runId))?.wire?.pikkuUserId,
|
|
1031
|
+
auditApproval: (event) => auditApprovalDecision(event),
|
|
1032
|
+
};
|
|
1343
1033
|
}
|
|
1344
|
-
async approveStep(runId, reason, decision) {
|
|
1345
|
-
|
|
1346
|
-
const stateKey = this.approvalStateKey(stepName);
|
|
1347
|
-
let resolved;
|
|
1348
|
-
try {
|
|
1349
|
-
resolved = await this.getStepState(runId, stepName);
|
|
1350
|
-
}
|
|
1351
|
-
catch {
|
|
1352
|
-
// knowledge: decisions/security/workflow-approval-payloads-are-validated-on-replay-inside-the-workflow.md
|
|
1353
|
-
}
|
|
1354
|
-
if (resolved?.stepId && resolved.status === 'succeeded') {
|
|
1355
|
-
const outcome = resolved.result;
|
|
1356
|
-
throw new WorkflowApprovalResolvedError(reason, outcome?.status ?? 'decided');
|
|
1357
|
-
}
|
|
1358
|
-
const state = await this.getRunState(runId);
|
|
1359
|
-
const record = (state[stateKey] ?? {});
|
|
1360
|
-
await this.updateRunState(runId, stateKey, {
|
|
1361
|
-
...record,
|
|
1362
|
-
decision,
|
|
1363
|
-
decidedAt: new Date().toISOString(),
|
|
1364
|
-
error: undefined,
|
|
1365
|
-
});
|
|
1366
|
-
await this.resumeWorkflow(runId);
|
|
1034
|
+
async approveStep(runId, reason, decision, session) {
|
|
1035
|
+
return recordApprovalDecision(this.approvalStore, runId, reason, decision, approvalDeciderFrom(session));
|
|
1367
1036
|
}
|
|
1368
1037
|
async approvalStep(runId, reason, options) {
|
|
1369
1038
|
const fromStepName = this.lastStepName(runId);
|
|
1370
|
-
const approvalStepName = this.nextStepKey(runId,
|
|
1371
|
-
return await this.withStepLock(runId, approvalStepName,
|
|
1372
|
-
let stepState;
|
|
1373
|
-
try {
|
|
1374
|
-
stepState = await this.getStepState(runId, approvalStepName);
|
|
1375
|
-
}
|
|
1376
|
-
catch {
|
|
1377
|
-
stepState = await this.insertStepState(runId, approvalStepName, 'pikkuWorkflowApproval', { reason, expiry: options.expiry }, undefined, fromStepName);
|
|
1378
|
-
}
|
|
1379
|
-
if (!stepState.stepId) {
|
|
1380
|
-
stepState = await this.insertStepState(runId, approvalStepName, 'pikkuWorkflowApproval', { reason, expiry: options.expiry }, undefined, fromStepName);
|
|
1381
|
-
}
|
|
1382
|
-
if (stepState.status === 'succeeded') {
|
|
1383
|
-
return stepState.result;
|
|
1384
|
-
}
|
|
1385
|
-
const stateKey = this.approvalStateKey(approvalStepName);
|
|
1386
|
-
let record = ((await this.getRunState(runId))[stateKey] ?? {});
|
|
1387
|
-
if (stepState.status === 'pending') {
|
|
1388
|
-
await this.setStepRunning(stepState.stepId);
|
|
1389
|
-
if (options.expiry !== undefined && !record.expiresAt) {
|
|
1390
|
-
const expiresAt = new Date(Date.now() + getDurationInMilliseconds(options.expiry)).toISOString();
|
|
1391
|
-
record = { ...record, expiresAt };
|
|
1392
|
-
await this.updateRunState(runId, stateKey, record);
|
|
1393
|
-
await this.scheduleRunWake(runId, getDurationInMilliseconds(options.expiry));
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
if (record.decision !== undefined) {
|
|
1397
|
-
const validation = await options.schema['~standard'].validate(record.decision);
|
|
1398
|
-
if (validation.issues) {
|
|
1399
|
-
await this.updateRunState(runId, stateKey, {
|
|
1400
|
-
...record,
|
|
1401
|
-
decision: undefined,
|
|
1402
|
-
decidedAt: undefined,
|
|
1403
|
-
error: validation.issues.map((issue) => ({
|
|
1404
|
-
message: issue.message,
|
|
1405
|
-
path: issue.path?.map((segment) => typeof segment === 'object' ? segment.key : segment),
|
|
1406
|
-
})),
|
|
1407
|
-
});
|
|
1408
|
-
throw new WorkflowSuspendedException(runId, reason);
|
|
1409
|
-
}
|
|
1410
|
-
const outcome = {
|
|
1411
|
-
status: 'decided',
|
|
1412
|
-
data: validation.value,
|
|
1413
|
-
};
|
|
1414
|
-
await this.setStepResult(stepState.stepId, outcome);
|
|
1415
|
-
return outcome;
|
|
1416
|
-
}
|
|
1417
|
-
if (record.expiresAt && Date.now() >= Date.parse(record.expiresAt)) {
|
|
1418
|
-
const outcome = { status: 'expired' };
|
|
1419
|
-
await this.setStepResult(stepState.stepId, outcome);
|
|
1420
|
-
return outcome;
|
|
1421
|
-
}
|
|
1422
|
-
throw new WorkflowSuspendedException(runId, reason);
|
|
1423
|
-
});
|
|
1039
|
+
const approvalStepName = this.nextStepKey(runId, approvalStepNameFor(reason));
|
|
1040
|
+
return await this.withStepLock(runId, approvalStepName, () => evaluateApprovalStep(this.approvalStore, runId, reason, approvalStepName, fromStepName, options));
|
|
1424
1041
|
}
|
|
1425
1042
|
createWorkflowWire(name, runId, rpcService, addonNamespace) {
|
|
1426
1043
|
const workflowWire = {
|
|
@@ -1466,40 +1083,15 @@ export class PikkuWorkflowService {
|
|
|
1466
1083
|
}
|
|
1467
1084
|
}
|
|
1468
1085
|
getConfig() {
|
|
1469
|
-
|
|
1470
|
-
const workflow = singletonServices.config?.workflow;
|
|
1471
|
-
return {
|
|
1472
|
-
retries: workflow?.retries ?? DEFAULT_STEP_RETRIES,
|
|
1473
|
-
retryDelay: workflow?.retryDelay ?? 0,
|
|
1474
|
-
orchestratorQueueName: workflow?.orchestratorQueueName ?? 'pikku-workflow-orchestrator',
|
|
1475
|
-
stepWorkerQueueName: workflow?.stepWorkerQueueName ?? 'pikku-workflow-step-worker',
|
|
1476
|
-
sleeperRPCName: workflow?.sleeperRPCName ?? 'pikkuWorkflowSleeper',
|
|
1477
|
-
};
|
|
1086
|
+
return resolveWorkflowConfig();
|
|
1478
1087
|
}
|
|
1479
1088
|
getOrchestratorQueueName(workflowName) {
|
|
1480
|
-
|
|
1481
|
-
const perWorkflow = `wf-orchestrator-${toKebab(workflowName)}`;
|
|
1482
|
-
const meta = pikkuState(null, 'queue', 'meta');
|
|
1483
|
-
if (meta[perWorkflow]) {
|
|
1484
|
-
return perWorkflow;
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
return this.getConfig().orchestratorQueueName;
|
|
1089
|
+
return orchestratorQueueName(this.queueStrategy, workflowName);
|
|
1488
1090
|
}
|
|
1489
1091
|
getStepWorkerQueueName(rpcName) {
|
|
1490
|
-
|
|
1491
|
-
const perStep = `wf-step-${toKebab(rpcName)}`;
|
|
1492
|
-
const meta = pikkuState(null, 'queue', 'meta');
|
|
1493
|
-
if (meta[perStep]) {
|
|
1494
|
-
return perStep;
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
return this.getConfig().stepWorkerQueueName;
|
|
1092
|
+
return stepWorkerQueueName(this.queueStrategy, rpcName);
|
|
1498
1093
|
}
|
|
1499
1094
|
getJobGroup(id) {
|
|
1500
|
-
|
|
1501
|
-
return undefined;
|
|
1502
|
-
}
|
|
1503
|
-
return { id, tier: id };
|
|
1095
|
+
return jobGroupFor(this.queueStrategy, id);
|
|
1504
1096
|
}
|
|
1505
1097
|
}
|