@pikku/core 0.12.78 → 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 +234 -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.d.ts +10 -0
- package/dist/services/in-memory-workflow-service.js +25 -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/utils/node-host-resolver.d.ts +12 -0
- package/dist/utils/node-host-resolver.js +16 -0
- package/dist/utils/safe-fetch.d.ts +18 -0
- package/dist/utils/safe-fetch.js +167 -29
- 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 +41 -5
- 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 -0
- 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 +44 -111
- package/dist/wirings/workflow/pikku-workflow-service.js +86 -404
- 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/gateway-handlers-run-through-the-function-runner-gate.md +13 -3
- 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 +14 -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 +25 -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/utils/node-host-resolver.ts +20 -0
- package/src/utils/safe-fetch.test.ts +143 -1
- package/src/utils/safe-fetch.ts +200 -25
- 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-authorization.test.ts +131 -0
- package/src/wirings/gateway/gateway-channel-meta.test.ts +44 -0
- package/src/wirings/gateway/gateway-runner.ts +62 -22
- 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 +69 -0
- package/src/wirings/secret/validate-secret-definitions.ts +2 -0
- 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 +208 -630
- 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-dispatch-relay.test.ts +128 -0
- 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
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { CoreSingletonServices } from '../../types/core.types.js'
|
|
2
|
+
import type { PikkuAIMiddlewareHooks } from './ai-agent.types.js'
|
|
3
|
+
|
|
4
|
+
/** Exactly what `modifyInput` accepts and returns, taken from the hook itself. */
|
|
5
|
+
type TurnInput = Pick<
|
|
6
|
+
Parameters<NonNullable<PikkuAIMiddlewareHooks['modifyInput']>>[1],
|
|
7
|
+
'messages' | 'instructions'
|
|
8
|
+
>
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Run every `modifyInput` hook in order, threading each result into the next.
|
|
12
|
+
*
|
|
13
|
+
* Shared by the four places a turn starts — first turn and resume, on both the
|
|
14
|
+
* streaming and non-streaming paths. `sharedNotes` is one bag per run that the
|
|
15
|
+
* caller keeps, because hooks use it to pass things forward (`voiceInput`
|
|
16
|
+
* leaves the transcript there for `voiceOutput`).
|
|
17
|
+
*/
|
|
18
|
+
export const applyInputMiddleware = async (
|
|
19
|
+
aiMiddlewares: PikkuAIMiddlewareHooks[],
|
|
20
|
+
singletonServices: CoreSingletonServices,
|
|
21
|
+
input: TurnInput,
|
|
22
|
+
sharedNotes: Record<string, unknown>
|
|
23
|
+
): Promise<TurnInput> => {
|
|
24
|
+
let { messages, instructions } = input
|
|
25
|
+
for (const mw of aiMiddlewares) {
|
|
26
|
+
if (!mw.modifyInput) continue
|
|
27
|
+
const result = await mw.modifyInput(singletonServices, {
|
|
28
|
+
messages,
|
|
29
|
+
instructions,
|
|
30
|
+
shared: sharedNotes,
|
|
31
|
+
})
|
|
32
|
+
messages = result.messages
|
|
33
|
+
instructions = result.instructions
|
|
34
|
+
}
|
|
35
|
+
return { messages, instructions }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The step fields both agent paths hand to `afterStep`, taken from the hook. */
|
|
39
|
+
type StepResult = Omit<
|
|
40
|
+
Parameters<NonNullable<PikkuAIMiddlewareHooks['afterStep']>>[1],
|
|
41
|
+
'stepNumber'
|
|
42
|
+
>
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Notify every `afterStep` hook. The payload is the step verbatim plus its
|
|
46
|
+
* ordinal — a hook that wants the run's totals accumulates them itself.
|
|
47
|
+
*/
|
|
48
|
+
export const notifyAfterStep = async (
|
|
49
|
+
aiMiddlewares: PikkuAIMiddlewareHooks[],
|
|
50
|
+
singletonServices: CoreSingletonServices,
|
|
51
|
+
stepNumber: number,
|
|
52
|
+
stepResult: StepResult
|
|
53
|
+
): Promise<void> => {
|
|
54
|
+
for (const mw of aiMiddlewares) {
|
|
55
|
+
if (!mw.afterStep) continue
|
|
56
|
+
await mw.afterStep(singletonServices, {
|
|
57
|
+
stepNumber,
|
|
58
|
+
text: stepResult.text,
|
|
59
|
+
toolCalls: stepResult.toolCalls,
|
|
60
|
+
toolResults: stepResult.toolResults,
|
|
61
|
+
usage: stepResult.usage,
|
|
62
|
+
finishReason: stepResult.finishReason,
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The step shape kept on the run: each tool call paired with its result.
|
|
69
|
+
*
|
|
70
|
+
* A result is stringified unless it already is a string, because this is what
|
|
71
|
+
* gets persisted and read back as history — an object there is unreadable.
|
|
72
|
+
*/
|
|
73
|
+
export const toAccumulatedStep = (stepResult: StepResult) => ({
|
|
74
|
+
usage: stepResult.usage,
|
|
75
|
+
toolCalls: stepResult.toolCalls.map((tc) => {
|
|
76
|
+
const tr = stepResult.toolResults.find(
|
|
77
|
+
(r) => r.toolCallId === tc.toolCallId
|
|
78
|
+
)
|
|
79
|
+
return {
|
|
80
|
+
name: tc.toolName,
|
|
81
|
+
args: tc.args as Record<string, unknown>,
|
|
82
|
+
result:
|
|
83
|
+
typeof tr?.result === 'string'
|
|
84
|
+
? tr.result
|
|
85
|
+
: JSON.stringify(tr?.result ?? ''),
|
|
86
|
+
}
|
|
87
|
+
}),
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
type PendingApproval = {
|
|
91
|
+
toolName: string
|
|
92
|
+
args: unknown
|
|
93
|
+
reason?: string
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
type ToolDef = {
|
|
97
|
+
name: string
|
|
98
|
+
approvalDescriptionFn?: (args: any) => Promise<string> | string
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Fill in each approval's human-readable reason from the tool's own
|
|
103
|
+
* `approvalDescriptionFn`, where it did not supply one.
|
|
104
|
+
*
|
|
105
|
+
* A description that throws is swallowed: the approval still has to be raised,
|
|
106
|
+
* and a gate that disappears because its label failed to render is the worst
|
|
107
|
+
* available outcome.
|
|
108
|
+
*/
|
|
109
|
+
export const describeApprovals = async (
|
|
110
|
+
approvals: PendingApproval[],
|
|
111
|
+
tools: ToolDef[]
|
|
112
|
+
): Promise<void> => {
|
|
113
|
+
for (const approval of approvals) {
|
|
114
|
+
if (approval.reason) continue
|
|
115
|
+
const toolDef = tools.find((t) => t.name === approval.toolName)
|
|
116
|
+
if (!toolDef?.approvalDescriptionFn) continue
|
|
117
|
+
try {
|
|
118
|
+
approval.reason = await toolDef.approvalDescriptionFn(approval.args)
|
|
119
|
+
} catch {}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -164,12 +164,7 @@ export const voiceInput = (config?: {
|
|
|
164
164
|
// to send, and a message with no content is not a question.
|
|
165
165
|
if (updatedContent.length === 0) throw new NoSpeechDetectedError()
|
|
166
166
|
|
|
167
|
-
//
|
|
168
|
-
// reads as non-speech and still have content — an image with a silent
|
|
169
|
-
// caption clip — which leaves nothing above to throw. Recording `''` here
|
|
170
|
-
// would send a transcript event saying the user said nothing, and a client
|
|
171
|
-
// that tells "not transcribed yet" from "transcribed" by the key being
|
|
172
|
-
// absent would render the turn as an empty bubble rather than a pending one.
|
|
167
|
+
// knowledge: decisions/internals/an-empty-transcript-is-not-recorded.md
|
|
173
168
|
if (heard.length > 0) {
|
|
174
169
|
shared[SPOKEN_TRANSCRIPT] = heard.join(' ')
|
|
175
170
|
}
|
|
@@ -206,11 +206,7 @@ export const voiceOutput = (config?: {
|
|
|
206
206
|
}
|
|
207
207
|
if (!aiAgentRunner?.generateSpeech) return event
|
|
208
208
|
|
|
209
|
-
//
|
|
210
|
-
// every turn it handles, so `false` means a real user really typed; the
|
|
211
|
-
// key being absent means nothing reported either way — no voice input is
|
|
212
|
-
// wired — and that caller's replies are spoken exactly as they were before
|
|
213
|
-
// this option existed.
|
|
209
|
+
// knowledge: decisions/internals/voice-output-speaks-unless-voice-input-explicitly-says-otherwise.md
|
|
214
210
|
if (!config?.always && shared[SPOKEN_TURN] === false) return event
|
|
215
211
|
|
|
216
212
|
/**
|
|
@@ -228,13 +224,7 @@ export const voiceOutput = (config?: {
|
|
|
228
224
|
)
|
|
229
225
|
}
|
|
230
226
|
|
|
231
|
-
//
|
|
232
|
-
// should still speak the half it can, and repeating the notice for
|
|
233
|
-
// every sentence of a long one would bury the reply itself.
|
|
234
|
-
//
|
|
235
|
-
// Per sentence is also what makes the voice right. A reply that answers
|
|
236
|
-
// in English and then quotes a Chinese title is two sentences in two
|
|
237
|
-
// scripts, and each is synthesized in the voice its own script needs.
|
|
227
|
+
// knowledge: decisions/internals/speech-synthesis-picks-a-voice-per-sentence-and-warns-once.md
|
|
238
228
|
let voice = config.voice
|
|
239
229
|
if (config.speakableScripts) {
|
|
240
230
|
const unspeakable = unspeakableScripts(text, config.speakableScripts)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CorePikkuChannelMiddleware } from './channel.types.js'
|
|
1
2
|
import type {
|
|
2
3
|
CoreSingletonServices,
|
|
3
4
|
CorePikkuMiddleware,
|
|
@@ -61,7 +62,9 @@ export const runChannelLifecycleWithMiddleware = async ({
|
|
|
61
62
|
`${channelConfig.name}:${lifecycleType}:cm`,
|
|
62
63
|
{
|
|
63
64
|
wireInheritedChannelMiddleware: channelMiddlewareMeta,
|
|
64
|
-
|
|
65
|
+
// knowledge: questions/channel-middleware-accepts-bare-factories-that-nothing-resolves.md
|
|
66
|
+
wireChannelMiddleware:
|
|
67
|
+
channelConfig.channelMiddleware as CorePikkuChannelMiddleware[],
|
|
65
68
|
}
|
|
66
69
|
)
|
|
67
70
|
|
|
@@ -76,7 +79,7 @@ export const runChannelLifecycleWithMiddleware = async ({
|
|
|
76
79
|
const runLifecycle = async () => {
|
|
77
80
|
return await runPikkuFunc('channel', channelConfig.name, meta.pikkuFuncId, {
|
|
78
81
|
singletonServices: services,
|
|
79
|
-
data: () => data
|
|
82
|
+
data: () => data,
|
|
80
83
|
wire,
|
|
81
84
|
sessionService: userSession,
|
|
82
85
|
tags: meta.tags ?? [],
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import { pikkuState, resetPikkuState } from '../../pikku-state.js'
|
|
5
|
+
import { wireChannel } from './channel-runner.js'
|
|
6
|
+
|
|
7
|
+
const registeredFunc = (funcId: string) =>
|
|
8
|
+
pikkuState(null, 'function', 'functions').get(funcId)
|
|
9
|
+
|
|
10
|
+
const wire = (channel: any) => {
|
|
11
|
+
resetPikkuState()
|
|
12
|
+
pikkuState(null, 'channel', 'meta', {
|
|
13
|
+
shapes: {
|
|
14
|
+
name: 'shapes',
|
|
15
|
+
route: '/shapes',
|
|
16
|
+
input: null,
|
|
17
|
+
connect: { pikkuFuncId: 'shapes__connect' },
|
|
18
|
+
disconnect: { pikkuFuncId: 'shapes__disconnect' },
|
|
19
|
+
message: { pikkuFuncId: 'shapes__message' },
|
|
20
|
+
messageWirings: {},
|
|
21
|
+
},
|
|
22
|
+
} as any)
|
|
23
|
+
wireChannel({ name: 'shapes', route: '/shapes', ...channel })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* knowledge: decisions/internals/channel-message-handlers-accept-three-config-shapes.md
|
|
28
|
+
*/
|
|
29
|
+
describe('every channel handler shape registers a callable function config', () => {
|
|
30
|
+
const handler = async () => undefined
|
|
31
|
+
|
|
32
|
+
test('a direct function config registers as-is', () => {
|
|
33
|
+
wire({ onConnect: { func: handler } })
|
|
34
|
+
|
|
35
|
+
assert.equal(typeof registeredFunc('shapes__connect')?.func, 'function')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('a wrapper around a function config registers the inner config', () => {
|
|
39
|
+
wire({ onConnect: { func: { func: handler }, middleware: [] } })
|
|
40
|
+
|
|
41
|
+
// Registering the wrapper itself leaves `.func` an object, and the function
|
|
42
|
+
// runner calls `.func(...)` — that is a TypeError at connect time.
|
|
43
|
+
assert.equal(
|
|
44
|
+
typeof registeredFunc('shapes__connect')?.func,
|
|
45
|
+
'function',
|
|
46
|
+
'the wrapper was registered instead of the config it wraps'
|
|
47
|
+
)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('onDisconnect unwraps the same way', () => {
|
|
51
|
+
wire({ onDisconnect: { func: { func: handler }, middleware: [] } })
|
|
52
|
+
|
|
53
|
+
assert.equal(typeof registeredFunc('shapes__disconnect')?.func, 'function')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('onMessage unwraps the same way', () => {
|
|
57
|
+
wire({ onMessage: { func: { func: handler } } })
|
|
58
|
+
|
|
59
|
+
assert.equal(typeof registeredFunc('shapes__message')?.func, 'function')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('a simple wrapper — a function plus sibling middleware — stays whole', () => {
|
|
63
|
+
wire({ onConnect: { func: handler, middleware: [] } })
|
|
64
|
+
|
|
65
|
+
const registered = registeredFunc('shapes__connect')
|
|
66
|
+
assert.equal(typeof registered?.func, 'function')
|
|
67
|
+
assert.ok(
|
|
68
|
+
'middleware' in (registered as object),
|
|
69
|
+
'the sibling middleware must survive registration'
|
|
70
|
+
)
|
|
71
|
+
})
|
|
72
|
+
})
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
CoreUserSession,
|
|
5
5
|
} from '../../types/core.types.js'
|
|
6
6
|
import type {
|
|
7
|
+
CorePikkuChannelMiddleware,
|
|
7
8
|
BinaryData,
|
|
8
9
|
ChannelMessageMeta,
|
|
9
10
|
CoreChannel,
|
|
@@ -94,7 +95,7 @@ export const processMessageHandlers = (
|
|
|
94
95
|
logger.error(
|
|
95
96
|
`Channel ${channelConfig.name} with id ${channelHandler.getChannel().channelId} requires a session for ${routeMessage}. No session is attached to this websocket connection. Ensure auth middleware establishes a session during websocket upgrade, and configure sessionStore when the runtime needs persisted sessions.`
|
|
96
97
|
)
|
|
97
|
-
//
|
|
98
|
+
// knowledge: questions/unauthorized-channel-replies-escape-the-declared-out-type.md
|
|
98
99
|
channelHandler.getChannel().send(`Unauthorized for ${routeMessage}`)
|
|
99
100
|
return
|
|
100
101
|
}
|
|
@@ -111,10 +112,7 @@ export const processMessageHandlers = (
|
|
|
111
112
|
// Get wire middleware: channel-level middleware + message-specific middleware
|
|
112
113
|
const channelWireMiddleware = channelConfig.middleware || []
|
|
113
114
|
|
|
114
|
-
//
|
|
115
|
-
// - Direct config: onMessage.func is a plain Function
|
|
116
|
-
// - Wrapper: onMessage.func is a CorePikkuFunctionConfig (has onMessage.func.func)
|
|
117
|
-
// - Simple wrapper: onMessage has both func (plain Function) and middleware properties
|
|
115
|
+
// knowledge: decisions/internals/channel-message-handlers-accept-three-config-shapes.md
|
|
118
116
|
const isWrapper =
|
|
119
117
|
onMessage &&
|
|
120
118
|
typeof onMessage === 'object' &&
|
|
@@ -145,7 +143,9 @@ export const processMessageHandlers = (
|
|
|
145
143
|
inheritedMiddleware,
|
|
146
144
|
wireMiddleware,
|
|
147
145
|
inheritedChannelMiddleware: channelMeta?.channelMiddleware,
|
|
148
|
-
|
|
146
|
+
// knowledge: questions/channel-middleware-accepts-bare-factories-that-nothing-resolves.md
|
|
147
|
+
wireChannelMiddleware:
|
|
148
|
+
wireChannelMiddleware as CorePikkuChannelMiddleware[],
|
|
149
149
|
coerceDataFromSchema: true,
|
|
150
150
|
tags: channelConfig.tags,
|
|
151
151
|
sessionService: userSession,
|
|
@@ -191,7 +191,7 @@ export const processMessageHandlers = (
|
|
|
191
191
|
|
|
192
192
|
processed = true
|
|
193
193
|
const routeResult = await processMessage(
|
|
194
|
-
data
|
|
194
|
+
data,
|
|
195
195
|
routes[routerValue],
|
|
196
196
|
routingProperty,
|
|
197
197
|
routerValue
|
|
@@ -10,20 +10,6 @@ import {
|
|
|
10
10
|
type ChannelRPCValidator,
|
|
11
11
|
} from './channel-rpc.types.js'
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
* `remote` for channels that only ever flow one way — a server-sent stream, an
|
|
15
|
-
* agent's output, a local CLI. Nothing is listening for a request on them, so
|
|
16
|
-
* the call is refused rather than waiting out a timeout.
|
|
17
|
-
*/
|
|
18
|
-
export const unsupportedChannelRemote = async (
|
|
19
|
-
funcName: string
|
|
20
|
-
): Promise<never> => {
|
|
21
|
-
throw new ChannelRPCError(
|
|
22
|
-
`Cannot call "${funcName}" remotely: this channel has no peer that answers`,
|
|
23
|
-
'unsupported'
|
|
24
|
-
)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
13
|
/**
|
|
28
14
|
* A `DeploymentService` whose transport is an open channel rather than an
|
|
29
15
|
* address, so a server-side function can call back into a peer that has no
|
|
@@ -109,21 +109,42 @@ describe('ChannelRPCRegistry', () => {
|
|
|
109
109
|
})
|
|
110
110
|
})
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
/**
|
|
113
|
+
* The registry unrefs its timeout timer so an in-flight call can never hold
|
|
114
|
+
* a process open. That makes the timeout unawaitable in a test: when the
|
|
115
|
+
* pending call is the only thing left, the loop drains before the timer
|
|
116
|
+
* fires, and node:test cancels the file — taking every later test with it.
|
|
117
|
+
*
|
|
118
|
+
* Driving the clock by hand removes the dependency on real time entirely.
|
|
119
|
+
* `t.mock.timers` is scoped to the test and resets itself, so the tests that
|
|
120
|
+
* do want a real clock are unaffected.
|
|
121
|
+
*
|
|
122
|
+
* knowledge: decisions/internals/an-unref-d-timer-cannot-be-awaited-under-node-test.md
|
|
123
|
+
*/
|
|
124
|
+
test('times out instead of hanging forever', async (t) => {
|
|
125
|
+
t.mock.timers.enable({ apis: ['setTimeout'] })
|
|
126
|
+
|
|
113
127
|
const registry = new ChannelRPCRegistry(10)
|
|
114
128
|
const call = registry.register()
|
|
115
129
|
|
|
116
|
-
|
|
130
|
+
const rejected = assert.rejects(call.promise, (e: ChannelRPCError) => {
|
|
117
131
|
assert.equal(e.reason, 'timeout')
|
|
118
132
|
return true
|
|
119
133
|
})
|
|
134
|
+
t.mock.timers.tick(10)
|
|
135
|
+
await rejected
|
|
136
|
+
|
|
120
137
|
assert.equal(registry.inFlight, 0, 'a timed out call is not leaked')
|
|
121
138
|
})
|
|
122
139
|
|
|
123
|
-
test('drops a late response for an already-timed-out call', async () => {
|
|
140
|
+
test('drops a late response for an already-timed-out call', async (t) => {
|
|
141
|
+
t.mock.timers.enable({ apis: ['setTimeout'] })
|
|
142
|
+
|
|
124
143
|
const registry = new ChannelRPCRegistry(10)
|
|
125
144
|
const call = registry.register()
|
|
126
|
-
|
|
145
|
+
const rejected = assert.rejects(call.promise)
|
|
146
|
+
t.mock.timers.tick(10)
|
|
147
|
+
await rejected
|
|
127
148
|
|
|
128
149
|
const settled = registry.settle({
|
|
129
150
|
action: CHANNEL_RPC_RESPONSE,
|
|
@@ -148,3 +148,17 @@ export type ChannelRPCValidator = (
|
|
|
148
148
|
funcName: string,
|
|
149
149
|
value: unknown
|
|
150
150
|
) => Promise<void> | void
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* `remote` for wires with no peer that answers — a server-sent stream, an
|
|
154
|
+
* agent's output, a local CLI. Nothing is listening for a request on them, so
|
|
155
|
+
* the call is refused rather than waiting out a timeout.
|
|
156
|
+
*/
|
|
157
|
+
export const unsupportedChannelRemote = async (
|
|
158
|
+
funcName: string
|
|
159
|
+
): Promise<never> => {
|
|
160
|
+
throw new ChannelRPCError(
|
|
161
|
+
`Cannot call "${funcName}" remotely: this channel has no peer that answers`,
|
|
162
|
+
'unsupported'
|
|
163
|
+
)
|
|
164
|
+
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { NotFoundError } from '../../errors/errors.js'
|
|
2
2
|
import { addFunction } from '../../function/function-runner.js'
|
|
3
|
-
import type {
|
|
3
|
+
import type { CorePikkuFunctionConfig } from '../../function/functions.types.js'
|
|
4
4
|
import { pikkuState, getSingletonServices } from '../../pikku-state.js'
|
|
5
5
|
import { coerceTopLevelDataFromSchema, validateSchema } from '../../schema.js'
|
|
6
6
|
import type { SessionService } from '../../services/user-session-service.js'
|
|
7
|
-
import type { CorePikkuMiddleware } from '../../types/core.types.js'
|
|
8
7
|
import { httpRouter } from '../http/routers/http-router.js'
|
|
9
8
|
import type {
|
|
10
9
|
ChannelMeta,
|
|
@@ -13,19 +12,34 @@ import type {
|
|
|
13
12
|
RunChannelParams,
|
|
14
13
|
} from './channel.types.js'
|
|
15
14
|
|
|
15
|
+
type HandlerConfig = CorePikkuFunctionConfig<any, any>
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The config to register for a channel handler: the handler itself when it is a
|
|
19
|
+
* direct function config, or its inner `func` when it is a wrapper.
|
|
20
|
+
*
|
|
21
|
+
* knowledge: decisions/internals/channel-message-handlers-accept-three-config-shapes.md
|
|
22
|
+
*/
|
|
23
|
+
const registeredHandler = (handler: unknown): HandlerConfig => {
|
|
24
|
+
const candidate = handler as { func?: unknown }
|
|
25
|
+
return typeof candidate.func === 'function'
|
|
26
|
+
? (handler as HandlerConfig)
|
|
27
|
+
: (candidate.func as HandlerConfig)
|
|
28
|
+
}
|
|
29
|
+
|
|
16
30
|
export const wireChannel = <
|
|
17
31
|
In,
|
|
18
32
|
Channel extends string,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
33
|
+
ChannelConnect,
|
|
34
|
+
ChannelDisconnect,
|
|
35
|
+
ChannelFunctionMessage,
|
|
22
36
|
>(
|
|
23
37
|
channel: CoreChannel<
|
|
24
38
|
In,
|
|
25
39
|
Channel,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
40
|
+
ChannelConnect,
|
|
41
|
+
ChannelDisconnect,
|
|
42
|
+
ChannelFunctionMessage
|
|
29
43
|
>
|
|
30
44
|
) => {
|
|
31
45
|
const channelsMeta = pikkuState(null, 'channel', 'meta')
|
|
@@ -38,19 +52,23 @@ export const wireChannel = <
|
|
|
38
52
|
}
|
|
39
53
|
|
|
40
54
|
if (channel.onConnect && channelMeta.connect) {
|
|
41
|
-
addFunction(
|
|
55
|
+
addFunction(
|
|
56
|
+
channelMeta.connect.pikkuFuncId,
|
|
57
|
+
registeredHandler(channel.onConnect)
|
|
58
|
+
)
|
|
42
59
|
}
|
|
43
60
|
|
|
44
61
|
if (channel.onDisconnect && channelMeta.disconnect) {
|
|
45
|
-
addFunction(
|
|
62
|
+
addFunction(
|
|
63
|
+
channelMeta.disconnect.pikkuFuncId,
|
|
64
|
+
registeredHandler(channel.onDisconnect)
|
|
65
|
+
)
|
|
46
66
|
}
|
|
47
67
|
|
|
48
68
|
if (channel.onMessage && channelMeta.message?.pikkuFuncId) {
|
|
49
69
|
addFunction(
|
|
50
70
|
channelMeta.message.pikkuFuncId,
|
|
51
|
-
(channel.onMessage
|
|
52
|
-
? channel.onMessage
|
|
53
|
-
: (channel.onMessage as any).func
|
|
71
|
+
registeredHandler(channel.onMessage)
|
|
54
72
|
)
|
|
55
73
|
}
|
|
56
74
|
|
|
@@ -63,17 +81,12 @@ export const wireChannel = <
|
|
|
63
81
|
const wiringMeta = channelWirings[wiringKey]
|
|
64
82
|
if (!wiringMeta) return
|
|
65
83
|
|
|
66
|
-
addFunction(
|
|
67
|
-
wiringMeta.pikkuFuncId,
|
|
68
|
-
(handler as any).func instanceof Function
|
|
69
|
-
? handler
|
|
70
|
-
: (handler as any).func
|
|
71
|
-
)
|
|
84
|
+
addFunction(wiringMeta.pikkuFuncId, registeredHandler(handler))
|
|
72
85
|
})
|
|
73
86
|
})
|
|
74
87
|
}
|
|
75
88
|
|
|
76
|
-
pikkuState(null, 'channel', 'channels').set(channel.name, channel
|
|
89
|
+
pikkuState(null, 'channel', 'channels').set(channel.name, channel)
|
|
77
90
|
}
|
|
78
91
|
|
|
79
92
|
const getMatchingChannelConfig = (path: string) => {
|
|
@@ -64,6 +64,8 @@ export interface ChannelMeta {
|
|
|
64
64
|
message: ChannelMessageMeta | null
|
|
65
65
|
messageWirings: Record<string, Record<string, ChannelMessageMeta>>
|
|
66
66
|
binary?: boolean | null
|
|
67
|
+
/** Set when the channel was wired by a gateway rather than declared directly. */
|
|
68
|
+
gateway?: boolean
|
|
67
69
|
summary?: string
|
|
68
70
|
description?: string
|
|
69
71
|
errors?: string[]
|
|
@@ -34,7 +34,8 @@ export abstract class PikkuAbstractChannelHandler<
|
|
|
34
34
|
setState: (s) => {
|
|
35
35
|
channelState = s
|
|
36
36
|
},
|
|
37
|
-
|
|
37
|
+
// knowledge: decisions/internals/channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md
|
|
38
|
+
getState: () => channelState as never,
|
|
38
39
|
clearState: () => {
|
|
39
40
|
channelState = undefined
|
|
40
41
|
},
|
|
@@ -117,7 +117,8 @@ export const runChannelConnect = async ({
|
|
|
117
117
|
channelName: channelConfig.name,
|
|
118
118
|
})
|
|
119
119
|
channel.setState = (state) => channelStore.setState(channelId, state)
|
|
120
|
-
|
|
120
|
+
// knowledge: decisions/internals/channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md
|
|
121
|
+
channel.getState = () => channelStore.getState(channelId) as never
|
|
121
122
|
channel.clearState = () => channelStore.clearState(channelId)
|
|
122
123
|
|
|
123
124
|
const wire: PikkuRawWire = {
|
|
@@ -198,7 +199,8 @@ export const runChannelDisconnect = async ({
|
|
|
198
199
|
channelName,
|
|
199
200
|
})
|
|
200
201
|
channel.setState = (state) => channelStore.setState(channelId, state)
|
|
201
|
-
|
|
202
|
+
// knowledge: decisions/internals/channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md
|
|
203
|
+
channel.getState = () => channelStore.getState(channelId) as never
|
|
202
204
|
channel.clearState = () => channelStore.clearState(channelId)
|
|
203
205
|
|
|
204
206
|
const userSession = new PikkuSessionService<CoreUserSession>(
|
|
@@ -271,7 +273,8 @@ export const runChannelMessage = async (
|
|
|
271
273
|
channelName,
|
|
272
274
|
})
|
|
273
275
|
channel.setState = (state) => channelStore.setState(channelId, state)
|
|
274
|
-
|
|
276
|
+
// knowledge: decisions/internals/channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md
|
|
277
|
+
channel.getState = () => channelStore.getState(channelId) as never
|
|
275
278
|
channel.clearState = () => channelStore.clearState(channelId)
|
|
276
279
|
|
|
277
280
|
const userSession = new PikkuSessionService<CoreUserSession>(
|
|
@@ -90,7 +90,9 @@ export async function executeCLIViaChannel({
|
|
|
90
90
|
return
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
// A CLI channel response is rendered off-process: there are no services
|
|
94
|
+
// to hand the renderer, and every CLI renderer ignores the argument.
|
|
95
|
+
renderer(null as never, response, undefined)
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
commandRoute.subscribe(commandId, responseHandler)
|
|
@@ -32,7 +32,10 @@ export type CorePikkuCLIClientRender<Data> = (
|
|
|
32
32
|
) => void | Promise<void>
|
|
33
33
|
|
|
34
34
|
/** The normal case once the server owns the command tree. */
|
|
35
|
-
const defaultJSONRenderer: CorePikkuCLIClientRender<any> = (
|
|
35
|
+
const defaultJSONRenderer: CorePikkuCLIClientRender<any> = (
|
|
36
|
+
_services,
|
|
37
|
+
data
|
|
38
|
+
) => {
|
|
36
39
|
console.log(JSON.stringify(data))
|
|
37
40
|
}
|
|
38
41
|
|
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
CLIMeta,
|
|
17
17
|
} from './cli.types.js'
|
|
18
18
|
import type {
|
|
19
|
+
CoreConfig,
|
|
19
20
|
CoreSingletonServices,
|
|
20
21
|
CoreServices,
|
|
21
22
|
CreateWireServices,
|
|
@@ -24,7 +25,7 @@ import type {
|
|
|
24
25
|
CreateSingletonServices,
|
|
25
26
|
} from '../../types/core.types.js'
|
|
26
27
|
import type { PikkuChannel } from '../channel/channel.types.js'
|
|
27
|
-
import { unsupportedChannelRemote } from '../channel/channel-rpc.js'
|
|
28
|
+
import { unsupportedChannelRemote } from '../channel/channel-rpc.types.js'
|
|
28
29
|
import {
|
|
29
30
|
PikkuSessionService,
|
|
30
31
|
createMiddlewareSessionWireProps,
|
|
@@ -321,7 +322,8 @@ export async function runCLICommand({
|
|
|
321
322
|
setState: (s) => {
|
|
322
323
|
cliState = s
|
|
323
324
|
},
|
|
324
|
-
|
|
325
|
+
// knowledge: decisions/internals/channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md
|
|
326
|
+
getState: () => cliState as never,
|
|
325
327
|
clearState: () => {
|
|
326
328
|
cliState = undefined
|
|
327
329
|
},
|
|
@@ -372,7 +374,8 @@ export async function runCLICommand({
|
|
|
372
374
|
if (result !== undefined && !onOutput) {
|
|
373
375
|
const commandRenderer = programData?.renderers[commandId]
|
|
374
376
|
const jsonMode =
|
|
375
|
-
(data as
|
|
377
|
+
(data as { json?: unknown; output?: unknown }).json === true ||
|
|
378
|
+
(data as { json?: unknown; output?: unknown }).output === 'json'
|
|
376
379
|
const finalRenderer: CorePikkuCLIRender<any> | undefined =
|
|
377
380
|
jsonMode && commandRenderer !== undefined
|
|
378
381
|
? defaultJSONRenderer
|
|
@@ -491,7 +494,7 @@ export async function executeCLI({
|
|
|
491
494
|
|
|
492
495
|
const config = createConfig
|
|
493
496
|
? await createConfig(new LocalVariablesService(), data)
|
|
494
|
-
: ({} as
|
|
497
|
+
: ({} as CoreConfig)
|
|
495
498
|
|
|
496
499
|
const singletonServices = await createSingletonServices(config)
|
|
497
500
|
pikkuState(null, 'package', 'singletonServices', singletonServices)
|