@pikku/core 0.12.79 → 0.12.80
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 +161 -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/function/function-runner.js +20 -42
- package/dist/function/functions.types.d.ts +9 -5
- package/dist/index.d.ts +4 -9
- package/dist/index.js +3 -8
- 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 +12 -5
- package/dist/schema.js +35 -1
- package/dist/services/ai-run-state-service.d.ts +7 -1
- package/dist/services/in-memory-ai-run-state-service.d.ts +1 -1
- package/dist/services/in-memory-ai-run-state-service.js +2 -1
- package/dist/services/in-memory-workflow-service.js +2 -0
- 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/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 +226 -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 -1
- package/dist/types/state.types.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-memory.js +3 -1
- package/dist/wirings/ai-agent/ai-agent-prepare.js +3 -9
- package/dist/wirings/ai-agent/ai-agent-runner.js +28 -107
- package/dist/wirings/ai-agent/ai-agent-stream.js +20 -61
- package/dist/wirings/ai-agent/ai-agent-turn.d.ts +56 -0
- package/dist/wirings/ai-agent/ai-agent-turn.js +81 -0
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +1 -1
- package/dist/wirings/ai-agent/voice-input.js +1 -6
- package/dist/wirings/ai-agent/voice-output.js +2 -12
- 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/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/cli-runner.js +4 -2
- package/dist/wirings/cli/cli.types.d.ts +0 -8
- package/dist/wirings/cli/command-parser.js +13 -0
- package/dist/wirings/gateway/gateway-runner.js +9 -2
- 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 +6 -13
- package/dist/wirings/http/http.types.d.ts +0 -10
- package/dist/wirings/http/index.d.ts +1 -1
- package/dist/wirings/http/index.js +1 -1
- package/dist/wirings/mcp/mcp-runner.d.ts +0 -7
- package/dist/wirings/mcp/mcp-runner.js +0 -6
- package/dist/wirings/rpc/rpc-runner.d.ts +8 -51
- package/dist/wirings/rpc/rpc-runner.js +53 -86
- package/dist/wirings/rpc/rpc-types.d.ts +3 -0
- package/dist/wirings/secret/validate-secret-definitions.js +2 -2
- 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/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 +29 -2
- 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 +7 -2
- package/dist/wirings/workflow/index.js +5 -1
- package/dist/wirings/workflow/pikku-scenario-service.d.ts +17 -3
- package/dist/wirings/workflow/pikku-scenario-service.js +38 -10
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +28 -147
- package/dist/wirings/workflow/pikku-workflow-service.js +71 -493
- package/dist/wirings/workflow/workflow-approval.d.ts +39 -0
- package/dist/wirings/workflow/workflow-approval.js +114 -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-engine.types.js +1 -0
- package/dist/wirings/workflow/workflow-run-ownership.d.ts +16 -0
- package/dist/wirings/workflow/workflow-run-ownership.js +29 -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 +7 -0
- 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/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-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 +38 -26
- 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/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-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 +34 -0
- package/knowledge/decisions/security/an-agent-approval-is-claimed-before-the-tool-runs.md +33 -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 +7 -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 +13 -3
- package/scripts/generate-api-report.d.mts +1 -0
- package/scripts/generate-api-report.mjs +89 -0
- package/scripts/generate-api-report.mts +218 -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/function/function-runner.test.ts +1 -1
- package/src/function/function-runner.ts +28 -39
- package/src/function/functions.types.ts +15 -9
- package/src/handle-error.ts +1 -1
- package/src/index.ts +3 -22
- 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 +17 -6
- package/src/public-surface.json +578 -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-run-state-service.ts +7 -1
- package/src/services/audit-service.ts +2 -2
- package/src/services/in-memory-ai-run-state-service.ts +3 -2
- package/src/services/in-memory-workflow-service.ts +2 -0
- package/src/services/index.ts +1 -4
- 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/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 +286 -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 +15 -1
- package/src/types/state.types.ts +1 -1
- 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-interrupt.test.ts +2 -1
- package/src/wirings/ai-agent/ai-agent-memory.ts +8 -1
- package/src/wirings/ai-agent/ai-agent-prepare.ts +10 -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 +49 -120
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +2 -0
- package/src/wirings/ai-agent/ai-agent-stream.ts +36 -63
- package/src/wirings/ai-agent/ai-agent-thread-ownership.test.ts +1 -1
- package/src/wirings/ai-agent/ai-agent-turn.ts +121 -0
- package/src/wirings/ai-agent/ai-agent.types.ts +1 -1
- package/src/wirings/ai-agent/voice-input.ts +1 -6
- package/src/wirings/ai-agent/voice-output.ts +2 -12
- 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/local/local-channel-runner.ts +1 -1
- 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-client-runner.ts +4 -1
- package/src/wirings/cli/cli-runner.ts +7 -4
- 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/gateway/gateway-channel-meta.test.ts +44 -0
- package/src/wirings/gateway/gateway-runner.ts +27 -19
- package/src/wirings/http/http-routes.ts +4 -1
- package/src/wirings/http/http-runner.ts +11 -26
- package/src/wirings/http/http.types.ts +0 -15
- package/src/wirings/http/index.ts +1 -7
- package/src/wirings/http/pikku-fetch-http-request.ts +2 -2
- package/src/wirings/http/web-request.ts +1 -1
- package/src/wirings/mcp/mcp-runner.ts +2 -16
- 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/queue-runner.ts +1 -1
- package/src/wirings/rpc/addon-auth-tags.test.ts +1 -5
- package/src/wirings/rpc/rpc-runner.ts +58 -136
- package/src/wirings/rpc/rpc-types.ts +7 -0
- package/src/wirings/rpc/wire-addon.ts +3 -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/pikku-trigger-service.ts +0 -5
- package/src/wirings/trigger/trigger-runner.ts +7 -5
- 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 +35 -2
- 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 +4 -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 +12 -4
- package/src/wirings/workflow/pikku-scenario-service.ts +54 -25
- package/src/wirings/workflow/pikku-workflow-service.test.ts +2 -2
- package/src/wirings/workflow/pikku-workflow-service.ts +190 -743
- package/src/wirings/workflow/scenario-hooks.test.ts +51 -0
- package/src/wirings/workflow/workflow-approval.ts +185 -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 +215 -0
- package/src/wirings/workflow/workflow-run-engine.types.ts +91 -0
- package/src/wirings/workflow/workflow-run-ownership.ts +36 -0
- package/src/wirings/workflow/workflow-suspend.ts +61 -0
- package/src/wirings/workflow/workflow.types.ts +7 -0
- package/src/wirings-stay-decoupled.test.ts +123 -0
- package/tsconfig.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/src/internal.ts +0 -10
|
@@ -143,8 +143,12 @@ const msg = (role: AIMessage['role'], content: string): AIMessage => ({
|
|
|
143
143
|
})
|
|
144
144
|
|
|
145
145
|
/** Read a structured result, falling back to parsing JSON out of the text. */
|
|
146
|
-
const readObject = <T>(result: {
|
|
147
|
-
|
|
146
|
+
const readObject = <T>(result: {
|
|
147
|
+
object?: unknown
|
|
148
|
+
text?: string
|
|
149
|
+
}): T | null => {
|
|
150
|
+
if (result.object && typeof result.object === 'object')
|
|
151
|
+
return result.object as T
|
|
148
152
|
if (result.text) {
|
|
149
153
|
try {
|
|
150
154
|
return JSON.parse(result.text) as T
|
|
@@ -201,7 +205,11 @@ export const rememberIds = (
|
|
|
201
205
|
return into
|
|
202
206
|
}
|
|
203
207
|
for (const [key, value] of Object.entries(body)) {
|
|
204
|
-
if (
|
|
208
|
+
if (
|
|
209
|
+
typeof value === 'string' &&
|
|
210
|
+
MEMORABLE.test(key) &&
|
|
211
|
+
value.length < 200
|
|
212
|
+
) {
|
|
205
213
|
into[key] = value
|
|
206
214
|
} else if (typeof value === 'object') {
|
|
207
215
|
rememberIds(value, into, depth + 1)
|
|
@@ -237,7 +245,10 @@ const personaInstructions = (
|
|
|
237
245
|
'',
|
|
238
246
|
agents.length
|
|
239
247
|
? `Assistants you can talk to:\n${agents
|
|
240
|
-
.map(
|
|
248
|
+
.map(
|
|
249
|
+
(agent) =>
|
|
250
|
+
`- ${agent.name}${agent.description ? `: ${agent.description}` : ''}`
|
|
251
|
+
)
|
|
241
252
|
.join('\n')}`
|
|
242
253
|
: '',
|
|
243
254
|
fixtures.length ? `Files you have: ${fixtures.join(', ')}` : '',
|
|
@@ -351,7 +362,10 @@ export const runVirtualUser = async (
|
|
|
351
362
|
stoppedBy = 'budget-duration'
|
|
352
363
|
break
|
|
353
364
|
}
|
|
354
|
-
if (
|
|
365
|
+
if (
|
|
366
|
+
budget?.mutations !== undefined &&
|
|
367
|
+
tally.mutations >= budget.mutations
|
|
368
|
+
) {
|
|
355
369
|
stoppedBy = 'budget-mutations'
|
|
356
370
|
break
|
|
357
371
|
}
|
|
@@ -402,10 +416,7 @@ export const runVirtualUser = async (
|
|
|
402
416
|
)
|
|
403
417
|
)
|
|
404
418
|
}
|
|
405
|
-
//
|
|
406
|
-
// `stale` almost always trusts them, a newcomer has none to trust, and an
|
|
407
|
-
// auditor checks nearly everything — which is the whole difference between
|
|
408
|
-
// those runs, expressed as one roll rather than as prose.
|
|
419
|
+
// knowledge: decisions/internals/a-virtual-user-decides-whether-to-trust-memory-once-per-turn.md
|
|
409
420
|
if (Object.keys(memory).length && !rng.chance(profile.reReadRate)) {
|
|
410
421
|
messages.push(
|
|
411
422
|
msg(
|
|
@@ -448,7 +459,9 @@ export const runVirtualUser = async (
|
|
|
448
459
|
|
|
449
460
|
if (!raw || typeof raw.kind !== 'string') {
|
|
450
461
|
steps.push(record)
|
|
451
|
-
messages.push(
|
|
462
|
+
messages.push(
|
|
463
|
+
msg('user', 'That was not a valid action. Choose one action.')
|
|
464
|
+
)
|
|
452
465
|
continue
|
|
453
466
|
}
|
|
454
467
|
|
|
@@ -550,10 +563,7 @@ export const runVirtualUser = async (
|
|
|
550
563
|
continue
|
|
551
564
|
}
|
|
552
565
|
|
|
553
|
-
//
|
|
554
|
-
// read-only disposition is never offered a mutation and an approval-gated
|
|
555
|
-
// endpoint is never offered at all, so `index` is the gate and there is no
|
|
556
|
-
// second one here to drift out of step with it.
|
|
566
|
+
// knowledge: decisions/internals/the-virtual-user-catalogue-is-the-only-gate-on-what-may-be-called.md
|
|
557
567
|
const mutating = !isReadOnly(entry)
|
|
558
568
|
|
|
559
569
|
const args = readArgs((action as { args?: unknown }).args)
|
|
@@ -566,7 +576,10 @@ export const runVirtualUser = async (
|
|
|
566
576
|
try {
|
|
567
577
|
response = await target.call(entry.name, args)
|
|
568
578
|
} catch (error) {
|
|
569
|
-
record.findingKinds = [
|
|
579
|
+
record.findingKinds = [
|
|
580
|
+
...(record.findingKinds ?? []),
|
|
581
|
+
'transport-error',
|
|
582
|
+
]
|
|
570
583
|
addFinding({
|
|
571
584
|
kind: 'transport-error',
|
|
572
585
|
detail: `${entry.name} threw: ${(error as Error).message}`,
|
|
@@ -45,7 +45,10 @@ describe('reachableAgents', () => {
|
|
|
45
45
|
|
|
46
46
|
test('carries the description through, which is what the model chooses on', () => {
|
|
47
47
|
const [router] = reachableAgents(AGENTS, ['content:write'])
|
|
48
|
-
assert.equal(
|
|
48
|
+
assert.equal(
|
|
49
|
+
router!.description,
|
|
50
|
+
'Routes requests to the right domain agent'
|
|
51
|
+
)
|
|
49
52
|
})
|
|
50
53
|
|
|
51
54
|
test('omits description rather than passing undefined', () => {
|
|
@@ -31,23 +31,9 @@ export const deriveCatalogue = (
|
|
|
31
31
|
): ApiCatalogueEntry[] => {
|
|
32
32
|
const entries: ApiCatalogueEntry[] = []
|
|
33
33
|
for (const [name, meta] of Object.entries(functions ?? {})) {
|
|
34
|
-
//
|
|
35
|
-
// held out of every deployed unit — so offering one would only ever waste
|
|
36
|
-
// a turn on a 404.
|
|
37
|
-
//
|
|
38
|
-
// For a platform or addon step this is not an efficiency argument but the
|
|
39
|
-
// oracle: a virtual user's findings are worth something only because it
|
|
40
|
-
// cannot manufacture the outcomes it is meant to discover. One that could
|
|
41
|
-
// invoke "Stripe's webhook arrives" would forge its own payment success,
|
|
42
|
-
// and every finding downstream of that is worthless. Same class of argument
|
|
43
|
-
// as `allowApprovalRequired` defaulting to false — and enforced here, at
|
|
44
|
-
// derivation, rather than by convention.
|
|
34
|
+
// knowledge: decisions/security/a-virtual-user-is-never-offered-a-step-that-would-forge-its-own-oracle.md
|
|
45
35
|
if (meta.scenario || meta.scenarioStep || meta.scenarioStepKind) continue
|
|
46
|
-
//
|
|
47
|
-
// what the shipped target calls over. Absent is not permissive: an unexposed
|
|
48
|
-
// function 404s, and offering one spends a step to learn nothing about the
|
|
49
|
-
// product. Measured on the e2e app, 34 of its 72 functions are in exactly
|
|
50
|
-
// that state — nearly half a catalogue that cannot be called.
|
|
36
|
+
// knowledge: decisions/internals/only-exposed-functions-enter-a-virtual-user-catalogue.md
|
|
51
37
|
if (meta.expose !== true) continue
|
|
52
38
|
|
|
53
39
|
const inputSchema = meta.inputSchemaName
|
|
@@ -103,10 +89,7 @@ const scenarioSteps = (meta: {
|
|
|
103
89
|
)
|
|
104
90
|
}
|
|
105
91
|
|
|
106
|
-
//
|
|
107
|
-
// the scenario declares them. Following `next` instead would buy an ordering
|
|
108
|
-
// that is already true and cost a traversal that has to decide what a branch
|
|
109
|
-
// means to a reader who will never take one.
|
|
92
|
+
// knowledge: decisions/internals/virtual-user-step-order-comes-from-insertion-order.md
|
|
110
93
|
return Object.values(meta.nodes ?? {}).flatMap((node) => {
|
|
111
94
|
const { rpcName, scenarioStepPhase, actor } = (node ?? {}) as {
|
|
112
95
|
rpcName?: string
|
|
@@ -149,11 +132,7 @@ export const deriveIntents = (
|
|
|
149
132
|
steps.push(
|
|
150
133
|
composeStepProse({
|
|
151
134
|
phase: step.phase,
|
|
152
|
-
//
|
|
153
|
-
// placeholders from a run that happened; there is no run yet, and
|
|
154
|
-
// "invites {email}" tells a user to pick someone where the filled-in
|
|
155
|
-
// form would have told it whom — which is the scenario's answer, not
|
|
156
|
-
// the user's own.
|
|
135
|
+
// knowledge: decisions/internals/a-scenario-step-template-is-offered-unfilled.md
|
|
157
136
|
description: template ?? description ?? '',
|
|
158
137
|
actor: step.actor,
|
|
159
138
|
})
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { strict as assert } from 'assert'
|
|
2
2
|
import { describe, test } from 'node:test'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
DISPOSITIONS,
|
|
5
|
+
dispositionProfile,
|
|
6
|
+
} from './virtual-user-dispositions.js'
|
|
4
7
|
|
|
5
8
|
describe('resolving a disposition profile', () => {
|
|
6
9
|
test('an unknown disposition falls back to realistic rather than throwing', () => {
|
|
@@ -46,7 +49,9 @@ describe('resolving a disposition profile', () => {
|
|
|
46
49
|
const profile = dispositionProfile('careless', {
|
|
47
50
|
instructions: 'Paste ids from other tabs.',
|
|
48
51
|
})
|
|
49
|
-
assert.ok(
|
|
52
|
+
assert.ok(
|
|
53
|
+
profile.instructions.startsWith(DISPOSITIONS.careless.instructions)
|
|
54
|
+
)
|
|
50
55
|
assert.ok(profile.instructions.endsWith('Paste ids from other tabs.'))
|
|
51
56
|
})
|
|
52
57
|
|
|
@@ -154,10 +154,7 @@ export const DISPOSITIONS: Readonly<
|
|
|
154
154
|
invertedOracle: true,
|
|
155
155
|
},
|
|
156
156
|
|
|
157
|
-
//
|
|
158
|
-
// it abandons rarely, runs cool, and is told to stop and say so rather than
|
|
159
|
-
// guess — the failure mode here is a wrong action nobody asked for, not a
|
|
160
|
-
// missed bug.
|
|
157
|
+
// knowledge: decisions/internals/the-worker-disposition-is-the-one-that-is-not-testing.md
|
|
161
158
|
accountable: {
|
|
162
159
|
...REALISTIC,
|
|
163
160
|
instructions: [
|
|
@@ -3,7 +3,10 @@ import assert from 'node:assert/strict'
|
|
|
3
3
|
|
|
4
4
|
import { IntentStack, intentsForPersona } from './virtual-user-intents.js'
|
|
5
5
|
import { createRng } from './virtual-user-rng.js'
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
DISPOSITIONS,
|
|
8
|
+
type DispositionProfile,
|
|
9
|
+
} from './virtual-user-dispositions.js'
|
|
7
10
|
import type { IntentSource } from './virtual-user.types.js'
|
|
8
11
|
|
|
9
12
|
const profileWith = (
|
|
@@ -182,7 +185,11 @@ describe('intents for an actor', () => {
|
|
|
182
185
|
const catalogue: IntentSource[] = [
|
|
183
186
|
{ id: 'a', title: 'invite a teammate', personas: ['orgAdmin'] },
|
|
184
187
|
{ id: 'b', title: 'read the dashboard' },
|
|
185
|
-
{
|
|
188
|
+
{
|
|
189
|
+
id: 'c',
|
|
190
|
+
title: 'deploy a stage',
|
|
191
|
+
personas: ['platformAdmin', 'orgAdmin'],
|
|
192
|
+
},
|
|
186
193
|
]
|
|
187
194
|
|
|
188
195
|
test('an actor gets what names it, plus anything that names nobody', () => {
|
|
@@ -201,7 +208,10 @@ describe('intents for an actor', () => {
|
|
|
201
208
|
|
|
202
209
|
test('an empty actor list means everyone', () => {
|
|
203
210
|
assert.deepEqual(
|
|
204
|
-
intentsForPersona(
|
|
211
|
+
intentsForPersona(
|
|
212
|
+
[{ id: 'x', title: 'anything', personas: [] }],
|
|
213
|
+
'nobody'
|
|
214
|
+
),
|
|
205
215
|
[{ id: 'x', title: 'anything', personas: [] }]
|
|
206
216
|
)
|
|
207
217
|
})
|
|
@@ -311,7 +311,7 @@ export interface ReturnStepMeta {
|
|
|
311
311
|
}
|
|
312
312
|
|
|
313
313
|
/**
|
|
314
|
-
* Inline step metadata
|
|
314
|
+
* Inline step metadata
|
|
315
315
|
*/
|
|
316
316
|
export interface InlineStepMeta {
|
|
317
317
|
/** Inline form - local execution */
|
|
@@ -528,7 +528,40 @@ export interface PikkuWorkflowWire {
|
|
|
528
528
|
approval: WorkflowWireApproval
|
|
529
529
|
}
|
|
530
530
|
|
|
531
|
-
|
|
531
|
+
/**
|
|
532
|
+
* What a scenario has accumulated so far, shared by its body and its hooks.
|
|
533
|
+
*
|
|
534
|
+
* Typed as a partial of the scenario's own output because a scenario can fail
|
|
535
|
+
* at any step: the shape describes what a *completed* run produces, and the
|
|
536
|
+
* context holds however much of it this run reached. A scenario declaring no
|
|
537
|
+
* object output gets an open record rather than `Partial<never>`.
|
|
538
|
+
*
|
|
539
|
+
* `any` collapses to `any`, not to the union of both branches. `PikkuWire`'s
|
|
540
|
+
* default depends on that: the wire type is also used as a generic *constraint*,
|
|
541
|
+
* and a constraint carrying a concrete context would reject every wire whose
|
|
542
|
+
* scenario output differs from it.
|
|
543
|
+
*/
|
|
544
|
+
export type ScenarioContext<Out = unknown> = 0 extends 1 & Out
|
|
545
|
+
? any
|
|
546
|
+
: Out extends object
|
|
547
|
+
? Partial<Out>
|
|
548
|
+
: Record<string, unknown>
|
|
549
|
+
|
|
550
|
+
export interface PikkuScenarioWire<Out = unknown> extends PikkuWorkflowWire {
|
|
551
|
+
/**
|
|
552
|
+
* Scratch the body writes and the `before`/`after` hooks read.
|
|
553
|
+
*
|
|
554
|
+
* A hook is a separate function, so it cannot see the body's locals — which
|
|
555
|
+
* is why teardown had nowhere to learn the ids the body minted. Assigning
|
|
556
|
+
* them here (`scenario.context.projectId = project.projectId`) hands them to
|
|
557
|
+
* `after`, which runs in a `finally` and so cleans up on a failed run too.
|
|
558
|
+
*
|
|
559
|
+
* Deliberately *not* a world: it is scoped to one run, and scenario steps
|
|
560
|
+
* cannot reach it. Steps stay pure functions of their declared inputs, so the
|
|
561
|
+
* ladder in the report remains a complete account of what a step saw.
|
|
562
|
+
*/
|
|
563
|
+
context: ScenarioContext<Out>
|
|
564
|
+
|
|
532
565
|
/**
|
|
533
566
|
* Durable polling step: invoke `rpcName` (as an actor when `options.actor`
|
|
534
567
|
* is set) until `predicate` passes or `options.within` elapses.
|
|
@@ -32,9 +32,11 @@ export const resolveFeatureScenarios = (
|
|
|
32
32
|
const scenarios = feature.scenarios ?? []
|
|
33
33
|
for (let index = 0; index < scenarios.length; index++) {
|
|
34
34
|
const entry = scenarios[index]!
|
|
35
|
-
const
|
|
35
|
+
const pairing =
|
|
36
36
|
typeof entry === 'object' && entry !== null && 'scenario' in entry
|
|
37
|
-
|
|
37
|
+
? (entry as { scenario: unknown; data?: unknown })
|
|
38
|
+
: undefined
|
|
39
|
+
const config = pairing ? pairing.scenario : entry
|
|
38
40
|
const scenarioName = nameByConfig.get(config)
|
|
39
41
|
if (!scenarioName) {
|
|
40
42
|
unresolved.push({ featureId, index })
|
|
@@ -44,10 +46,11 @@ export const resolveFeatureScenarios = (
|
|
|
44
46
|
featureId,
|
|
45
47
|
featureName: feature.name ?? featureId,
|
|
46
48
|
scenarioName,
|
|
47
|
-
data:
|
|
49
|
+
data: pairing?.data,
|
|
48
50
|
tags: [
|
|
49
51
|
...new Set([
|
|
50
|
-
...((registrations.get(scenarioName)?.func as
|
|
52
|
+
...((registrations.get(scenarioName)?.func as { tags?: string[] })
|
|
53
|
+
?.tags ?? []),
|
|
51
54
|
...(feature.tags ?? []),
|
|
52
55
|
]),
|
|
53
56
|
],
|
|
@@ -65,7 +65,7 @@ export function createGraph<RPCMap extends Record<string, RPCHandler>>() {
|
|
|
65
65
|
for (const [nodeId, def] of Object.entries(nodes) as [string, any][]) {
|
|
66
66
|
result[nodeId] = {
|
|
67
67
|
func: funcMap[nodeId] as string,
|
|
68
|
-
input: def?.input
|
|
68
|
+
input: def?.input,
|
|
69
69
|
next: def?.next,
|
|
70
70
|
onError: def?.onError,
|
|
71
71
|
retries: def?.retries,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import type { WorkflowRuntimeMeta } from '../workflow.types.js'
|
|
11
11
|
import { pikkuState } from '../../../pikku-state.js'
|
|
12
12
|
import { RPCNotFoundError } from '../../rpc/rpc-runner.js'
|
|
13
|
-
import { DEFAULT_STEP_RETRIES } from '../
|
|
13
|
+
import { DEFAULT_STEP_RETRIES } from '../workflow-constants.js'
|
|
14
14
|
|
|
15
15
|
describe('graph-runner bugs', () => {
|
|
16
16
|
test('continueGraph should NOT mark workflow completed while nodes are still running', async () => {
|
|
@@ -828,7 +828,9 @@ describe('graph-runner bugs', () => {
|
|
|
828
828
|
name: 'testRetries',
|
|
829
829
|
pikkuFuncId: 'testRetries',
|
|
830
830
|
source: 'graph',
|
|
831
|
-
|
|
831
|
+
// All three are dependency-free, so all three are entry nodes — a
|
|
832
|
+
// `startNode` may only name one the graph declared.
|
|
833
|
+
entryNodeIds: ['a', 'b', 'c'],
|
|
832
834
|
graphHash: 'retries-hash',
|
|
833
835
|
nodes: {
|
|
834
836
|
a: {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import type { PikkuWorkflowService } from '../pikku-workflow-service.js'
|
|
1
2
|
import {
|
|
2
|
-
type PikkuWorkflowService,
|
|
3
3
|
WorkflowAsyncException,
|
|
4
4
|
WorkflowSuspendedException,
|
|
5
|
-
|
|
6
|
-
} from '../
|
|
5
|
+
} from '../workflow-errors.js'
|
|
6
|
+
import { DEFAULT_STEP_RETRIES } from '../workflow-constants.js'
|
|
7
7
|
import type { GraphWireState, PikkuGraphWire } from './workflow-graph.types.js'
|
|
8
8
|
import { pikkuState, getSingletonServices } from '../../../pikku-state.js'
|
|
9
9
|
import type { WorkflowRuntimeMeta, WorkflowRunWire } from '../workflow.types.js'
|
|
@@ -211,7 +211,7 @@ function isDataRef(value: unknown): value is { $ref: string; path?: string } {
|
|
|
211
211
|
typeof value === 'object' &&
|
|
212
212
|
value !== null &&
|
|
213
213
|
'$ref' in value &&
|
|
214
|
-
typeof (value as
|
|
214
|
+
typeof (value as { $ref?: unknown }).$ref === 'string'
|
|
215
215
|
)
|
|
216
216
|
}
|
|
217
217
|
|
|
@@ -227,7 +227,7 @@ function isTemplate(value: unknown): value is TemplateValue {
|
|
|
227
227
|
typeof value === 'object' &&
|
|
228
228
|
value !== null &&
|
|
229
229
|
'$template' in value &&
|
|
230
|
-
typeof (value as
|
|
230
|
+
typeof (value as { $template?: unknown }).$template === 'object'
|
|
231
231
|
)
|
|
232
232
|
}
|
|
233
233
|
|
|
@@ -695,14 +695,6 @@ export async function executeGraphStep(
|
|
|
695
695
|
}
|
|
696
696
|
}
|
|
697
697
|
|
|
698
|
-
export async function onGraphNodeComplete(
|
|
699
|
-
workflowService: PikkuWorkflowService,
|
|
700
|
-
runId: string,
|
|
701
|
-
graphName: string
|
|
702
|
-
): Promise<void> {
|
|
703
|
-
await continueGraph(workflowService, runId, graphName)
|
|
704
|
-
}
|
|
705
|
-
|
|
706
698
|
export async function runFromMeta(
|
|
707
699
|
workflowService: PikkuWorkflowService,
|
|
708
700
|
runId: string,
|
|
@@ -928,9 +920,14 @@ export async function runWorkflowGraph(
|
|
|
928
920
|
}
|
|
929
921
|
|
|
930
922
|
const nodes = meta.nodes
|
|
931
|
-
const
|
|
932
|
-
|
|
933
|
-
|
|
923
|
+
const declaredEntryNodes = meta.entryNodeIds ?? []
|
|
924
|
+
// knowledge: decisions/security/a-graph-run-starts-at-an-entry-node-the-graph-declared.md
|
|
925
|
+
if (startNode && !declaredEntryNodes.includes(startNode)) {
|
|
926
|
+
throw new Error(
|
|
927
|
+
`Workflow graph '${graphName}': '${startNode}' is not an entry node`
|
|
928
|
+
)
|
|
929
|
+
}
|
|
930
|
+
const entryNodes: string[] = startNode ? [startNode] : declaredEntryNodes
|
|
934
931
|
validateGraphReferences(graphName, nodes, entryNodes)
|
|
935
932
|
|
|
936
933
|
if (entryNodes.length === 0) {
|
|
@@ -16,11 +16,6 @@ export const isRef = (value: unknown): value is RefValue =>
|
|
|
16
16
|
'__isRef' in value &&
|
|
17
17
|
(value as RefValue).__isRef === true
|
|
18
18
|
|
|
19
|
-
export interface InputRef {
|
|
20
|
-
nodeId: string
|
|
21
|
-
path?: string
|
|
22
|
-
}
|
|
23
|
-
|
|
24
19
|
export type RefFn<NodeIds extends string = string> = (
|
|
25
20
|
nodeId: NodeIds,
|
|
26
21
|
path?: string
|
|
@@ -1,20 +1,28 @@
|
|
|
1
|
+
export { PikkuWorkflowService } from './pikku-workflow-service.js'
|
|
1
2
|
export {
|
|
2
|
-
PikkuWorkflowService,
|
|
3
3
|
WorkflowCancelledException,
|
|
4
4
|
WorkflowSuspendedException,
|
|
5
5
|
WorkflowDispatchException,
|
|
6
6
|
WorkflowNotFoundError,
|
|
7
7
|
WorkflowRunNotFoundError,
|
|
8
8
|
WorkflowApprovalResolvedError,
|
|
9
|
-
|
|
10
|
-
} from './
|
|
9
|
+
WorkflowStepFunctionMismatchError,
|
|
10
|
+
} from './workflow-errors.js'
|
|
11
|
+
export { DEFAULT_STEP_RETRIES } from './workflow-constants.js'
|
|
12
|
+
export {
|
|
13
|
+
assertWorkflowRunOwner,
|
|
14
|
+
WorkflowRunForbiddenError,
|
|
15
|
+
} from './workflow-run-ownership.js'
|
|
11
16
|
export type {
|
|
12
17
|
RunLifecycleContext,
|
|
13
18
|
WorkflowRunEngine,
|
|
14
19
|
WorkflowRunExtension,
|
|
15
|
-
} from './
|
|
20
|
+
} from './workflow-run-engine.types.js'
|
|
16
21
|
export { deriveInvocationId, uuidv5 } from './workflow-invocation-id.js'
|
|
17
22
|
|
|
23
|
+
export { isRef } from './graph/workflow-graph.types.js'
|
|
24
|
+
export type { RefValue } from './graph/workflow-graph.types.js'
|
|
25
|
+
|
|
18
26
|
export {
|
|
19
27
|
buildRunTimeline,
|
|
20
28
|
reconstructStateAt,
|
|
@@ -9,13 +9,13 @@ import { closeWireServices } from '../../utils.js'
|
|
|
9
9
|
import { PikkuError, addError } from '../../errors/error-handler.js'
|
|
10
10
|
import { InMemoryWorkflowService } from '../../services/in-memory-workflow-service.js'
|
|
11
11
|
import { runScheduledTask } from '../scheduler/scheduler-runner.js'
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} from './
|
|
18
|
-
import type {
|
|
12
|
+
import { WorkflowStepNameNotString } from './workflow-errors.js'
|
|
13
|
+
import type {
|
|
14
|
+
RunLifecycleContext,
|
|
15
|
+
WorkflowRunEngine,
|
|
16
|
+
WorkflowRunExtension,
|
|
17
|
+
} from './workflow-run-engine.types.js'
|
|
18
|
+
import type { PikkuRawWire } from '../../types/core.types.js'
|
|
19
19
|
import type { ScenarioPersonas } from '../../services/personas-service.js'
|
|
20
20
|
import type { CorePikkuFunctionConfig } from '../../function/functions.types.js'
|
|
21
21
|
import type {
|
|
@@ -66,7 +66,7 @@ export const createScenarioRunner = (
|
|
|
66
66
|
type ScenarioHook = (
|
|
67
67
|
services: any,
|
|
68
68
|
data: any,
|
|
69
|
-
wire:
|
|
69
|
+
wire: PikkuRawWire
|
|
70
70
|
) => Promise<void> | void
|
|
71
71
|
|
|
72
72
|
/**
|
|
@@ -245,6 +245,10 @@ export class PikkuScenarioService implements WorkflowRunExtension {
|
|
|
245
245
|
// Scenario actors per run: live authenticated clients (cookie jars) are
|
|
246
246
|
// process-local by nature, so they ride this map, never the persisted wire.
|
|
247
247
|
private runActors = new Map<string, ScenarioPersonas>()
|
|
248
|
+
// What each run has accumulated so far. Process-local like the actors above:
|
|
249
|
+
// a scenario is a single in-process run, so the body and its hooks share one
|
|
250
|
+
// object rather than reading it back off the persisted wire.
|
|
251
|
+
private runContexts = new Map<string, Record<string, unknown>>()
|
|
248
252
|
private scenarioBrowserProvider?: ScenarioBrowserProvider
|
|
249
253
|
private scenarioEnvironment?: ScenarioEnvironment
|
|
250
254
|
private runSurface: ScenarioSurface = 'default'
|
|
@@ -304,14 +308,42 @@ export class PikkuScenarioService implements WorkflowRunExtension {
|
|
|
304
308
|
if (actors) {
|
|
305
309
|
this.runActors.set(runId, actors)
|
|
306
310
|
}
|
|
311
|
+
if (workflowMeta.source === 'scenario') {
|
|
312
|
+
this.runContexts.set(runId, {})
|
|
313
|
+
}
|
|
307
314
|
}
|
|
308
315
|
|
|
309
316
|
public detachRunContext(runId: string): void {
|
|
310
317
|
this.runActors.delete(runId)
|
|
318
|
+
this.runContexts.delete(runId)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* What the run has accumulated, for a reporter that wants to say what a
|
|
323
|
+
* failed scenario left behind. Undefined for a plain workflow.
|
|
324
|
+
*/
|
|
325
|
+
public getRunContext(runId: string): Record<string, unknown> | undefined {
|
|
326
|
+
return this.runContexts.get(runId)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* The run's context, created on demand.
|
|
331
|
+
*
|
|
332
|
+
* `attachRunContext` seeds it for an ordinary scenario run, but the wire is
|
|
333
|
+
* also built on paths that skip attach; without this the body would be
|
|
334
|
+
* writing to an `undefined` and teardown would read nothing.
|
|
335
|
+
*/
|
|
336
|
+
private contextForRun(runId: string): Record<string, unknown> {
|
|
337
|
+
let runContext = this.runContexts.get(runId)
|
|
338
|
+
if (!runContext) {
|
|
339
|
+
runContext = {}
|
|
340
|
+
this.runContexts.set(runId, runContext)
|
|
341
|
+
}
|
|
342
|
+
return runContext
|
|
311
343
|
}
|
|
312
344
|
|
|
313
345
|
public decorateRunWire(
|
|
314
|
-
wire:
|
|
346
|
+
wire: PikkuRawWire,
|
|
315
347
|
context: {
|
|
316
348
|
runId: string
|
|
317
349
|
workflowMeta: any
|
|
@@ -409,7 +441,7 @@ export class PikkuScenarioService implements WorkflowRunExtension {
|
|
|
409
441
|
phase: 'before' | 'after',
|
|
410
442
|
scenarioName: string,
|
|
411
443
|
hook: ScenarioHook,
|
|
412
|
-
wire:
|
|
444
|
+
wire: PikkuRawWire,
|
|
413
445
|
data: unknown,
|
|
414
446
|
packageName: string | null
|
|
415
447
|
): Promise<void> {
|
|
@@ -424,10 +456,9 @@ export class PikkuScenarioService implements WorkflowRunExtension {
|
|
|
424
456
|
|
|
425
457
|
let wireServices: Record<string, unknown> | undefined
|
|
426
458
|
try {
|
|
427
|
-
wireServices = (await createWireServices?.(
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
)) as Record<string, unknown> | undefined
|
|
459
|
+
wireServices = (await createWireServices?.(singletonServices, wire)) as
|
|
460
|
+
| Record<string, unknown>
|
|
461
|
+
| undefined
|
|
431
462
|
const services =
|
|
432
463
|
wireServices && Object.keys(wireServices).length > 0
|
|
433
464
|
? { ...singletonServices, ...wireServices }
|
|
@@ -508,6 +539,11 @@ export class PikkuScenarioService implements WorkflowRunExtension {
|
|
|
508
539
|
rpcService,
|
|
509
540
|
})
|
|
510
541
|
Object.assign(workflowWire, {
|
|
542
|
+
// One object per run, resolved through the map rather than created here,
|
|
543
|
+
// so the body and its before/after hooks share the same scratch even
|
|
544
|
+
// though the hooks are separate functions that cannot see its locals.
|
|
545
|
+
context: this.contextForRun(runId),
|
|
546
|
+
|
|
511
547
|
// Durable polling step: invoke an RPC (as an actor when options.as is
|
|
512
548
|
// set) until the predicate passes or `within` elapses. The whole poll is
|
|
513
549
|
// ONE recorded step, so replay returns the cached outcome.
|
|
@@ -648,12 +684,7 @@ export class PikkuScenarioService implements WorkflowRunExtension {
|
|
|
648
684
|
)
|
|
649
685
|
},
|
|
650
686
|
|
|
651
|
-
//
|
|
652
|
-
// `given`/`when` are sugar for each other, differing only in the
|
|
653
|
-
// prose a reporter renders. `then` is not: the phase is what decides
|
|
654
|
-
// whether the step's bindings are alternatives or witnesses, so the same
|
|
655
|
-
// step function called two ways runs differently. See
|
|
656
|
-
// {@link resolveScenarioSurfaces}.
|
|
687
|
+
// knowledge: decisions/internals/scenario-given-and-when-are-sugar-but-then-is-not.md
|
|
657
688
|
given: (stepName, stepFunc, data, options) =>
|
|
658
689
|
this.scenarioStep(
|
|
659
690
|
'given',
|
|
@@ -696,8 +727,7 @@ export class PikkuScenarioService implements WorkflowRunExtension {
|
|
|
696
727
|
data?: any,
|
|
697
728
|
options?: ScenarioStepOptions
|
|
698
729
|
): Promise<any> {
|
|
699
|
-
const { runId, workflowName, addonNamespace, workflowWire
|
|
700
|
-
context
|
|
730
|
+
const { runId, workflowName, addonNamespace, workflowWire } = context
|
|
701
731
|
// Also the guard for `then` being a wire member: an accidental
|
|
702
732
|
// `await scenario` calls it with a resolve function, which lands here as a
|
|
703
733
|
// loud, named error instead of a silent hang.
|
|
@@ -727,11 +757,10 @@ export class PikkuScenarioService implements WorkflowRunExtension {
|
|
|
727
757
|
stepName,
|
|
728
758
|
async () => {
|
|
729
759
|
const runOnSurface = async (surface: ScenarioSurface) => {
|
|
730
|
-
|
|
760
|
+
// No `rpc` yet — runPikkuFunc attaches it lazily for the invocation.
|
|
761
|
+
const wire: PikkuRawWire = {
|
|
731
762
|
workflow: workflowWire,
|
|
732
763
|
scenario: workflowWire,
|
|
733
|
-
rpc: rpcService?.wire?.rpc,
|
|
734
|
-
session: rpcService?.wire?.session,
|
|
735
764
|
pikkuUserId: workflowWire.pikkuUserId,
|
|
736
765
|
actors: this.runActors.get(runId),
|
|
737
766
|
scenarioStep: {
|
|
@@ -8,8 +8,8 @@ import { addWorkflow } from './dsl/workflow-runner.js'
|
|
|
8
8
|
import {
|
|
9
9
|
WorkflowApprovalResolvedError,
|
|
10
10
|
WorkflowSuspendedException,
|
|
11
|
-
|
|
12
|
-
} from './
|
|
11
|
+
} from './workflow-errors.js'
|
|
12
|
+
import type { PikkuWorkflowWire } from './workflow.types.js'
|
|
13
13
|
|
|
14
14
|
describe('pikku-workflow-service worker registration', () => {
|
|
15
15
|
test('registers sleeper function metadata after wireQueueWorkers', () => {
|