@pikku/core 0.12.79 → 0.12.82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +473 -0
- package/dist/dev/hot-reload.js +1 -6
- package/dist/ecosystem.d.ts +27 -0
- package/dist/ecosystem.js +26 -0
- package/dist/errors/error-handler.js +4 -2
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/function/function-runner.js +22 -47
- package/dist/function/functions.types.d.ts +9 -5
- package/dist/function/index.d.ts +1 -1
- package/dist/index.d.ts +14 -19
- package/dist/index.js +6 -11
- package/dist/middleware/auth-cookie.d.ts +0 -4
- package/dist/middleware/auth-cookie.js +38 -2
- package/dist/middleware/cors.js +1 -0
- package/dist/middleware/index.d.ts +1 -1
- package/dist/middleware/index.js +1 -1
- package/dist/middleware/remote-auth.js +14 -3
- package/dist/permissions.d.ts +8 -10
- package/dist/permissions.js +0 -11
- package/dist/pikku-state.js +16 -5
- package/dist/schema.js +35 -1
- package/dist/services/ai-agent-runner-service.d.ts +7 -0
- package/dist/services/ai-run-state-service.d.ts +17 -1
- package/dist/services/in-memory-ai-run-state-service.d.ts +6 -2
- package/dist/services/in-memory-ai-run-state-service.js +11 -1
- package/dist/services/in-memory-workflow-service.js +2 -0
- package/dist/services/index.d.ts +15 -15
- package/dist/services/index.js +5 -5
- package/dist/services/local-content-request-handler.d.ts +21 -0
- package/dist/services/local-content-request-handler.js +72 -53
- package/dist/services/local-content.d.ts +6 -0
- package/dist/services/local-content.js +14 -1
- package/dist/services/scoped-credential-service.d.ts +21 -0
- package/dist/services/scoped-credential-service.js +53 -0
- package/dist/services/workflow-service.d.ts +4 -2
- package/dist/testing/service-tests/agent-run-service-tests.d.ts +10 -0
- package/dist/testing/service-tests/agent-run-service-tests.js +72 -0
- package/dist/testing/service-tests/ai-storage-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/ai-storage-service-tests.js +302 -0
- package/dist/testing/service-tests/channel-store-tests.d.ts +3 -0
- package/dist/testing/service-tests/channel-store-tests.js +72 -0
- package/dist/testing/service-tests/credential-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/credential-service-tests.js +109 -0
- package/dist/testing/service-tests/deployment-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/deployment-service-tests.js +21 -0
- package/dist/testing/service-tests/event-hub-store-tests.d.ts +3 -0
- package/dist/testing/service-tests/event-hub-store-tests.js +34 -0
- package/dist/testing/service-tests/secret-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/secret-service-tests.js +80 -0
- package/dist/testing/service-tests/session-store-tests.d.ts +3 -0
- package/dist/testing/service-tests/session-store-tests.js +43 -0
- package/dist/testing/service-tests/workflow-run-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/workflow-run-service-tests.js +42 -0
- package/dist/testing/service-tests/workflow-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/workflow-service-tests.js +150 -0
- package/dist/testing/service-tests.d.ts +6 -0
- package/dist/testing/service-tests.js +26 -791
- package/dist/types/core.types.d.ts +11 -3
- package/dist/types/state.types.d.ts +14 -1
- package/dist/wirings/actor-flow/index.d.ts +1 -1
- package/dist/wirings/actor-flow/run-conversation.js +1 -6
- package/dist/wirings/ai-agent/agent-rpc.d.ts +15 -0
- package/dist/wirings/ai-agent/agent-rpc.js +53 -0
- package/dist/wirings/ai-agent/ai-agent-agui.js +1 -5
- package/dist/wirings/ai-agent/ai-agent-finalize.d.ts +58 -0
- package/dist/wirings/ai-agent/ai-agent-finalize.js +138 -0
- package/dist/wirings/ai-agent/ai-agent-interrupt.js +1 -0
- package/dist/wirings/ai-agent/ai-agent-memory.d.ts +2 -8
- package/dist/wirings/ai-agent/ai-agent-memory.js +37 -18
- package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +7 -0
- package/dist/wirings/ai-agent/ai-agent-model-config.js +44 -1
- package/dist/wirings/ai-agent/ai-agent-prepare.js +5 -9
- package/dist/wirings/ai-agent/ai-agent-runner.js +89 -147
- package/dist/wirings/ai-agent/ai-agent-stream.js +109 -97
- package/dist/wirings/ai-agent/ai-agent-turn.d.ts +57 -0
- package/dist/wirings/ai-agent/ai-agent-turn.js +82 -0
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +47 -2
- package/dist/wirings/ai-agent/index.d.ts +8 -7
- package/dist/wirings/ai-agent/index.js +5 -4
- package/dist/wirings/ai-agent/voice-input.js +1 -6
- package/dist/wirings/ai-agent/voice-output.js +2 -12
- package/dist/wirings/ai-scorer/ai-scorer-grade.d.ts +26 -0
- package/dist/wirings/ai-scorer/ai-scorer-grade.js +33 -0
- package/dist/wirings/ai-scorer/ai-scorer-judge.d.ts +17 -0
- package/dist/wirings/ai-scorer/ai-scorer-judge.js +92 -0
- package/dist/wirings/ai-scorer/ai-scorer-live.d.ts +15 -0
- package/dist/wirings/ai-scorer/ai-scorer-live.js +38 -0
- package/dist/wirings/ai-scorer/ai-scorer-registry.d.ts +18 -0
- package/dist/wirings/ai-scorer/ai-scorer-registry.js +46 -0
- package/dist/wirings/ai-scorer/ai-scorer-sampling.d.ts +8 -0
- package/dist/wirings/ai-scorer/ai-scorer-sampling.js +31 -0
- package/dist/wirings/ai-scorer/ai-scorer-snapshots.d.ts +10 -0
- package/dist/wirings/ai-scorer/ai-scorer-snapshots.js +40 -0
- package/dist/wirings/ai-scorer/ai-scorer-worker.d.ts +15 -0
- package/dist/wirings/ai-scorer/ai-scorer-worker.js +58 -0
- package/dist/wirings/ai-scorer/ai-scorer.d.ts +39 -0
- package/dist/wirings/ai-scorer/ai-scorer.js +40 -0
- package/dist/wirings/ai-scorer/ai-scorer.types.d.ts +90 -0
- package/dist/wirings/ai-scorer/ai-scorer.types.js +4 -0
- package/dist/wirings/ai-scorer/index.d.ts +6 -0
- package/dist/wirings/ai-scorer/index.js +5 -0
- package/dist/wirings/channel/channel-common.js +1 -0
- package/dist/wirings/channel/channel-handler.js +3 -5
- package/dist/wirings/channel/channel-rpc-service.d.ts +0 -6
- package/dist/wirings/channel/channel-rpc-service.js +0 -8
- package/dist/wirings/channel/channel-rpc.types.d.ts +6 -0
- package/dist/wirings/channel/channel-rpc.types.js +8 -0
- package/dist/wirings/channel/channel-runner.d.ts +1 -3
- package/dist/wirings/channel/channel-runner.js +16 -8
- package/dist/wirings/channel/channel.types.d.ts +2 -0
- package/dist/wirings/channel/index.d.ts +5 -6
- package/dist/wirings/channel/index.js +3 -4
- package/dist/wirings/channel/local/local-channel-runner.js +8 -1
- package/dist/wirings/channel/pikku-abstract-channel-handler.js +1 -0
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +3 -0
- package/dist/wirings/cli/channel/cli-channel-runner.js +2 -0
- package/dist/wirings/cli/channel/cli-raw-channel-runner.js +9 -1
- package/dist/wirings/cli/channel/index.d.ts +1 -2
- package/dist/wirings/cli/channel/index.js +0 -1
- package/dist/wirings/cli/cli-runner.js +17 -3
- package/dist/wirings/cli/cli.types.d.ts +0 -8
- package/dist/wirings/cli/command-parser.js +13 -0
- package/dist/wirings/credential/index.d.ts +1 -1
- package/dist/wirings/gateway/gateway-runner.js +9 -2
- package/dist/wirings/gateway/index.d.ts +1 -1
- package/dist/wirings/http/http-routes.js +2 -0
- package/dist/wirings/http/http-runner.d.ts +0 -10
- package/dist/wirings/http/http-runner.js +14 -15
- package/dist/wirings/http/http.types.d.ts +0 -10
- package/dist/wirings/http/index.d.ts +2 -3
- package/dist/wirings/http/index.js +1 -1
- package/dist/wirings/mcp/index.d.ts +1 -1
- package/dist/wirings/mcp/mcp-runner.d.ts +15 -7
- package/dist/wirings/mcp/mcp-runner.js +18 -11
- package/dist/wirings/persona/index.d.ts +3 -4
- package/dist/wirings/persona/index.js +2 -3
- package/dist/wirings/queue/index.d.ts +1 -3
- package/dist/wirings/queue/index.js +1 -3
- package/dist/wirings/rpc/addon-runner.d.ts +4 -0
- package/dist/wirings/rpc/addon-runner.js +19 -3
- package/dist/wirings/rpc/rpc-runner.d.ts +8 -51
- package/dist/wirings/rpc/rpc-runner.js +55 -86
- package/dist/wirings/rpc/rpc-types.d.ts +7 -0
- package/dist/wirings/rpc/wire-addon.d.ts +13 -0
- package/dist/wirings/rpc/wire-addon.js +4 -0
- package/dist/wirings/scheduler/index.d.ts +1 -1
- package/dist/wirings/secret/validate-secret-definitions.js +2 -2
- package/dist/wirings/trigger/index.d.ts +1 -1
- package/dist/wirings/trigger/pikku-trigger-service.d.ts +0 -4
- package/dist/wirings/trigger/trigger-runner.js +1 -0
- package/dist/wirings/virtual-user/index.d.ts +5 -6
- package/dist/wirings/virtual-user/index.js +2 -4
- package/dist/wirings/virtual-user/run-virtual-user.js +11 -11
- package/dist/wirings/virtual-user/virtual-user-derive.js +4 -25
- package/dist/wirings/virtual-user/virtual-user-dispositions.js +1 -4
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +100 -3
- package/dist/wirings/workflow/feature.js +7 -4
- package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -2
- package/dist/wirings/workflow/graph/graph-runner.js +8 -7
- package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +0 -4
- package/dist/wirings/workflow/index.d.ts +12 -7
- package/dist/wirings/workflow/index.js +7 -3
- package/dist/wirings/workflow/pikku-scenario-service.d.ts +17 -3
- package/dist/wirings/workflow/pikku-scenario-service.js +64 -10
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +28 -147
- package/dist/wirings/workflow/pikku-workflow-service.js +85 -493
- package/dist/wirings/workflow/scenario-step.types.d.ts +8 -0
- package/dist/wirings/workflow/workflow-approval-audit.d.ts +16 -0
- package/dist/wirings/workflow/workflow-approval-audit.js +40 -0
- package/dist/wirings/workflow/workflow-approval-policy.d.ts +20 -0
- package/dist/wirings/workflow/workflow-approval-policy.js +48 -0
- package/dist/wirings/workflow/workflow-approval.d.ts +67 -0
- package/dist/wirings/workflow/workflow-approval.js +177 -0
- package/dist/wirings/workflow/workflow-constants.d.ts +26 -0
- package/dist/wirings/workflow/workflow-constants.js +35 -0
- package/dist/wirings/workflow/workflow-errors.d.ts +58 -0
- package/dist/wirings/workflow/workflow-errors.js +112 -0
- package/dist/wirings/workflow/workflow-meta-resolver.d.ts +11 -0
- package/dist/wirings/workflow/workflow-meta-resolver.js +31 -0
- package/dist/wirings/workflow/workflow-queue-routing.d.ts +8 -0
- package/dist/wirings/workflow/workflow-queue-routing.js +38 -0
- package/dist/wirings/workflow/workflow-queue-wiring.d.ts +20 -0
- package/dist/wirings/workflow/workflow-queue-wiring.js +79 -0
- package/dist/wirings/workflow/workflow-recovery.d.ts +68 -0
- package/dist/wirings/workflow/workflow-recovery.js +101 -0
- package/dist/wirings/workflow/workflow-run-engine.types.d.ts +54 -0
- package/dist/wirings/workflow/workflow-run-ownership.d.ts +17 -0
- package/dist/wirings/workflow/workflow-run-ownership.js +30 -0
- package/dist/wirings/workflow/workflow-suspend.d.ts +12 -0
- package/dist/wirings/workflow/workflow-suspend.js +33 -0
- package/dist/wirings/workflow/workflow.types.d.ts +8 -1
- package/knowledge/decisions/internals/a-non-streaming-agent-run-registers-with-airunstate-too.md +22 -0
- package/knowledge/decisions/internals/a-resumed-agent-turn-is-as-interruptible-as-the-first.md +20 -0
- package/knowledge/decisions/internals/a-scenario-step-template-is-offered-unfilled.md +21 -0
- package/knowledge/decisions/internals/a-virtual-user-decides-whether-to-trust-memory-once-per-turn.md +21 -0
- package/knowledge/decisions/internals/a-wall-clock-threshold-is-a-load-test-in-disguise.md +46 -0
- package/knowledge/decisions/internals/a-workflow-wire-is-built-from-the-run-not-from-the-rpc-service.md +45 -0
- package/knowledge/decisions/internals/addon-pikku-meta-ships-at-the-package-root-or-under-dist.md +32 -0
- package/knowledge/decisions/internals/agent-context-waits-for-a-tool-result-still-being-written.md +21 -0
- package/knowledge/decisions/internals/agent-speech-travels-as-a-custom-agui-event.md +22 -0
- package/knowledge/decisions/internals/an-addon-scope-root-loses-to-a-root-the-host-already-declares.md +39 -0
- package/knowledge/decisions/internals/an-agent-interrupt-is-not-a-failure.md +23 -0
- package/knowledge/decisions/internals/an-agent-run-owned-by-another-instance-says-so.md +25 -0
- package/knowledge/decisions/internals/an-agent-stream-send-must-return-the-inner-sends-promise.md +22 -0
- package/knowledge/decisions/internals/an-empty-text-part-is-omitted-from-an-agent-message.md +20 -0
- package/knowledge/decisions/internals/an-empty-transcript-is-not-recorded.md +22 -0
- package/knowledge/decisions/internals/an-unref-d-timer-cannot-be-awaited-under-node-test.md +66 -0
- package/knowledge/decisions/internals/channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md +33 -0
- package/knowledge/decisions/internals/gateway-listener-middleware-runs-without-an-rpc-on-the-wire.md +43 -0
- package/knowledge/decisions/internals/hot-reload-writes-into-the-function-map-captured-at-startup.md +26 -0
- package/knowledge/decisions/internals/index.md +42 -3
- package/knowledge/decisions/internals/only-exposed-functions-enter-a-virtual-user-catalogue.md +21 -0
- package/knowledge/decisions/internals/scenario-given-and-when-are-sugar-but-then-is-not.md +24 -0
- package/knowledge/decisions/internals/side-effects-are-an-allowlist-not-a-boolean.md +34 -0
- package/knowledge/decisions/internals/speech-synthesis-picks-a-voice-per-sentence-and-warns-once.md +24 -0
- package/knowledge/decisions/internals/the-actor-prompt-says-json-because-of-json-object-mode.md +22 -0
- package/knowledge/decisions/internals/the-agent-done-event-goes-through-the-middleware-and-is-awaited.md +26 -0
- package/knowledge/decisions/internals/the-api-report-pins-members-not-just-names.md +43 -0
- package/knowledge/decisions/internals/the-ecosystem-entry-point-carries-the-adapter-surface.md +58 -0
- package/knowledge/decisions/internals/the-middleware-resolution-cache-is-deliberately-unbounded.md +40 -0
- package/knowledge/decisions/internals/the-per-invocation-rpc-view-is-a-class.md +40 -0
- package/knowledge/decisions/internals/the-persona-runtime-is-exported-from-the-persona-entry-point.md +28 -0
- package/knowledge/decisions/internals/the-transcript-event-is-sent-ahead-of-the-run.md +25 -0
- package/knowledge/decisions/internals/the-virtual-user-catalogue-is-the-only-gate-on-what-may-be-called.md +21 -0
- package/knowledge/decisions/internals/the-worker-disposition-is-the-one-that-is-not-testing.md +22 -0
- package/knowledge/decisions/internals/thread-history-records-the-transcript-not-the-audio.md +25 -0
- package/knowledge/decisions/internals/validate-runs-checks-by-precondition.md +115 -0
- package/knowledge/decisions/internals/virtual-user-step-order-comes-from-insertion-order.md +21 -0
- package/knowledge/decisions/internals/voice-output-speaks-unless-voice-input-explicitly-says-otherwise.md +24 -0
- package/knowledge/decisions/internals/wiring-registries-erase-the-generics-their-wire-functions-capture.md +35 -0
- package/knowledge/decisions/security/a-dropped-audit-write-is-always-logged.md +4 -2
- package/knowledge/decisions/security/a-function-never-receives-the-secret-service.md +37 -0
- package/knowledge/decisions/security/a-graph-run-starts-at-an-entry-node-the-graph-declared.md +33 -0
- package/knowledge/decisions/security/a-permission-gets-a-wire-it-cannot-reply-on.md +33 -0
- package/knowledge/decisions/security/a-step-runs-the-function-the-workflow-dispatched-it-with.md +37 -0
- package/knowledge/decisions/security/a-virtual-user-is-never-offered-a-step-that-would-forge-its-own-oracle.md +28 -0
- package/knowledge/decisions/security/a-workflow-run-is-read-and-approved-by-its-owner.md +50 -0
- package/knowledge/decisions/security/an-agent-approval-is-claimed-before-the-tool-runs.md +33 -0
- package/knowledge/decisions/security/an-approval-answer-outlives-the-run-it-answered.md +59 -0
- package/knowledge/decisions/security/an-upload-is-counted-as-it-arrives-not-buffered-then-measured.md +24 -0
- package/knowledge/decisions/security/index.md +9 -0
- package/knowledge/questions/channel-middleware-accepts-bare-factories-that-nothing-resolves.md +35 -0
- package/knowledge/questions/index.md +2 -1
- package/knowledge/questions/unauthorized-channel-replies-escape-the-declared-out-type.md +44 -0
- package/package.json +15 -3
- package/scripts/generate-api-report.d.mts +1 -0
- package/scripts/generate-api-report.mjs +89 -0
- package/scripts/generate-api-report.mts +343 -0
- package/src/api-report.test.ts +69 -0
- package/src/column-form.ts +1 -4
- package/src/crypto-utils.test.ts +15 -2
- package/src/dev/hot-reload.ts +2 -7
- package/src/ecosystem.ts +40 -0
- package/src/errors/error-handler.ts +5 -3
- package/src/errors/error.test.ts +4 -1
- package/src/errors/index.ts +1 -1
- package/src/function/function-runner.test.ts +53 -1
- package/src/function/function-runner.ts +33 -48
- package/src/function/functions.types.ts +15 -9
- package/src/function/index.ts +0 -2
- package/src/handle-error.ts +1 -1
- package/src/index.ts +3 -57
- package/src/middleware/auth-cookie.test.ts +50 -0
- package/src/middleware/auth-cookie.ts +38 -2
- package/src/middleware/cors.ts +2 -1
- package/src/middleware/index.ts +1 -1
- package/src/middleware/remote-auth.test.ts +43 -0
- package/src/middleware/remote-auth.ts +17 -3
- package/src/middleware-runner.ts +4 -2
- package/src/no-any-casts.test.ts +57 -0
- package/src/permissions.test.ts +3 -1
- package/src/permissions.ts +22 -24
- package/src/pikku-state.ts +22 -6
- package/src/public-surface.json +554 -0
- package/src/public-surface.json.README +25 -0
- package/src/public-surface.test.ts +105 -0
- package/src/removed-legacy-exports.test.ts +62 -0
- package/src/schema.test.ts +78 -0
- package/src/schema.ts +36 -1
- package/src/services/ai-agent-runner-service.ts +12 -1
- package/src/services/ai-run-state-service.ts +18 -1
- package/src/services/audit-service.ts +2 -2
- package/src/services/in-memory-ai-run-state-service.ts +16 -2
- package/src/services/in-memory-workflow-service.ts +2 -0
- package/src/services/index.ts +4 -47
- package/src/services/local-content-request-handler.test.ts +43 -5
- package/src/services/local-content-request-handler.ts +103 -74
- package/src/services/local-content.ts +15 -1
- package/src/services/local-email-service.ts +5 -1
- package/src/services/scoped-credential-service.test.ts +86 -0
- package/src/services/scoped-credential-service.ts +63 -0
- package/src/services/system-role-guard.test.ts +4 -1
- package/src/services/workflow-service.ts +9 -2
- package/src/side-effects-are-declared.test.ts +84 -0
- package/src/source-files-stay-composable.test.ts +41 -0
- package/src/testing/service-tests/agent-run-service-tests.ts +98 -0
- package/src/testing/service-tests/ai-storage-service-tests.ts +379 -0
- package/src/testing/service-tests/channel-store-tests.ts +98 -0
- package/src/testing/service-tests/credential-service-tests.ts +143 -0
- package/src/testing/service-tests/deployment-service-tests.ts +33 -0
- package/src/testing/service-tests/event-hub-store-tests.ts +48 -0
- package/src/testing/service-tests/secret-service-tests.ts +105 -0
- package/src/testing/service-tests/session-store-tests.ts +62 -0
- package/src/testing/service-tests/workflow-run-service-tests.ts +59 -0
- package/src/testing/service-tests/workflow-service-tests.ts +308 -0
- package/src/testing/service-tests.ts +31 -1111
- package/src/types/core.types.ts +14 -3
- package/src/types/state.types.ts +17 -1
- package/src/wirings/actor-flow/index.ts +0 -3
- package/src/wirings/actor-flow/run-conversation.ts +1 -6
- package/src/wirings/ai-agent/agent-rpc.ts +120 -0
- package/src/wirings/ai-agent/ai-agent-agui.ts +1 -5
- package/src/wirings/ai-agent/ai-agent-finalize.test.ts +186 -0
- package/src/wirings/ai-agent/ai-agent-finalize.ts +197 -0
- package/src/wirings/ai-agent/ai-agent-interrupt.test.ts +2 -1
- package/src/wirings/ai-agent/ai-agent-interrupt.ts +1 -0
- package/src/wirings/ai-agent/ai-agent-memory.ts +62 -39
- package/src/wirings/ai-agent/ai-agent-model-config.test.ts +72 -3
- package/src/wirings/ai-agent/ai-agent-model-config.ts +49 -1
- package/src/wirings/ai-agent/ai-agent-prepare.ts +12 -12
- package/src/wirings/ai-agent/ai-agent-resume-authorization.test.ts +1 -0
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +82 -2
- package/src/wirings/ai-agent/ai-agent-runner.ts +120 -160
- package/src/wirings/ai-agent/ai-agent-stream-output-hooks.test.ts +353 -0
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +2 -0
- package/src/wirings/ai-agent/ai-agent-stream.ts +152 -117
- package/src/wirings/ai-agent/ai-agent-thread-ownership.test.ts +1 -1
- package/src/wirings/ai-agent/ai-agent-turn.test.ts +67 -0
- package/src/wirings/ai-agent/ai-agent-turn.ts +122 -0
- package/src/wirings/ai-agent/ai-agent.types.ts +65 -5
- package/src/wirings/ai-agent/index.ts +2 -16
- package/src/wirings/ai-agent/voice-input.ts +1 -6
- package/src/wirings/ai-agent/voice-output.ts +2 -12
- package/src/wirings/ai-scorer/ai-scorer-grade.test.ts +106 -0
- package/src/wirings/ai-scorer/ai-scorer-grade.ts +55 -0
- package/src/wirings/ai-scorer/ai-scorer-judge.test.ts +143 -0
- package/src/wirings/ai-scorer/ai-scorer-judge.ts +120 -0
- package/src/wirings/ai-scorer/ai-scorer-live.test.ts +174 -0
- package/src/wirings/ai-scorer/ai-scorer-live.ts +56 -0
- package/src/wirings/ai-scorer/ai-scorer-registry.ts +63 -0
- package/src/wirings/ai-scorer/ai-scorer-sampling.test.ts +34 -0
- package/src/wirings/ai-scorer/ai-scorer-sampling.ts +36 -0
- package/src/wirings/ai-scorer/ai-scorer-snapshots.test.ts +49 -0
- package/src/wirings/ai-scorer/ai-scorer-snapshots.ts +46 -0
- package/src/wirings/ai-scorer/ai-scorer-worker.test.ts +122 -0
- package/src/wirings/ai-scorer/ai-scorer-worker.ts +69 -0
- package/src/wirings/ai-scorer/ai-scorer.ts +76 -0
- package/src/wirings/ai-scorer/ai-scorer.types.ts +107 -0
- package/src/wirings/ai-scorer/index.ts +24 -0
- package/src/wirings/channel/channel-common.ts +5 -2
- package/src/wirings/channel/channel-handler-shapes.test.ts +72 -0
- package/src/wirings/channel/channel-handler.ts +7 -7
- package/src/wirings/channel/channel-rpc-service.ts +0 -14
- package/src/wirings/channel/channel-rpc.test.ts +25 -4
- package/src/wirings/channel/channel-rpc.types.ts +14 -0
- package/src/wirings/channel/channel-runner.ts +33 -20
- package/src/wirings/channel/channel.types.ts +2 -0
- package/src/wirings/channel/index.ts +1 -20
- package/src/wirings/channel/local/local-channel-runner.test.ts +68 -0
- package/src/wirings/channel/local/local-channel-runner.ts +9 -2
- package/src/wirings/channel/pikku-abstract-channel-handler.ts +2 -1
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +6 -3
- package/src/wirings/cli/channel/cli-channel-runner.ts +3 -1
- package/src/wirings/cli/channel/cli-raw-channel-runner.test.ts +23 -0
- package/src/wirings/cli/channel/cli-raw-channel-runner.ts +12 -1
- package/src/wirings/cli/channel/cli-raw-client-runner.ts +4 -1
- package/src/wirings/cli/channel/index.ts +0 -7
- package/src/wirings/cli/cli-runner.test.ts +68 -0
- package/src/wirings/cli/cli-runner.ts +25 -5
- package/src/wirings/cli/cli.types.ts +0 -39
- package/src/wirings/cli/command-parser.test.ts +19 -0
- package/src/wirings/cli/command-parser.ts +17 -1
- package/src/wirings/credential/index.ts +0 -1
- package/src/wirings/gateway/gateway-channel-meta.test.ts +44 -0
- package/src/wirings/gateway/gateway-runner.ts +27 -19
- package/src/wirings/gateway/index.ts +0 -3
- package/src/wirings/http/http-routes.ts +4 -1
- package/src/wirings/http/http-runner.test.ts +66 -0
- package/src/wirings/http/http-runner.ts +21 -28
- package/src/wirings/http/http.types.ts +0 -15
- package/src/wirings/http/index.ts +2 -8
- package/src/wirings/http/pikku-fetch-http-request.ts +2 -2
- package/src/wirings/http/web-request.ts +1 -1
- package/src/wirings/mcp/index.ts +0 -1
- package/src/wirings/mcp/mcp-runner.test.ts +181 -0
- package/src/wirings/mcp/mcp-runner.ts +37 -21
- package/src/wirings/persona/index.ts +0 -8
- package/src/wirings/persona/persona-environments.test.ts +14 -3
- package/src/wirings/persona/persona.test.ts +13 -3
- package/src/wirings/persona/validate-personas.ts +5 -1
- package/src/wirings/queue/index.ts +0 -14
- package/src/wirings/queue/queue-runner.ts +1 -1
- package/src/wirings/rpc/addon-auth-tags.test.ts +1 -5
- package/src/wirings/rpc/addon-runner.ts +34 -3
- package/src/wirings/rpc/addon-secrets.test.ts +261 -0
- package/src/wirings/rpc/rpc-runner.test.ts +2 -0
- package/src/wirings/rpc/rpc-runner.ts +60 -136
- package/src/wirings/rpc/rpc-types.ts +11 -0
- package/src/wirings/rpc/wire-addon.ts +20 -1
- package/src/wirings/scheduler/index.ts +0 -1
- package/src/wirings/secret/validate-secret-definitions.test.ts +22 -0
- package/src/wirings/secret/validate-secret-definitions.ts +2 -2
- package/src/wirings/trigger/index.ts +0 -1
- package/src/wirings/trigger/pikku-trigger-service.ts +0 -5
- package/src/wirings/trigger/trigger-runner.ts +7 -5
- package/src/wirings/virtual-user/index.ts +0 -16
- package/src/wirings/virtual-user/run-virtual-user.test.ts +18 -9
- package/src/wirings/virtual-user/run-virtual-user.ts +28 -15
- package/src/wirings/virtual-user/virtual-user-agents.test.ts +4 -1
- package/src/wirings/virtual-user/virtual-user-derive.ts +4 -25
- package/src/wirings/virtual-user/virtual-user-dispositions.test.ts +7 -2
- package/src/wirings/virtual-user/virtual-user-dispositions.ts +1 -4
- package/src/wirings/virtual-user/virtual-user-intents.test.ts +13 -3
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +117 -4
- package/src/wirings/workflow/feature.ts +7 -4
- package/src/wirings/workflow/graph/graph-node.ts +1 -1
- package/src/wirings/workflow/graph/graph-runner.test.ts +76 -2
- package/src/wirings/workflow/graph/graph-runner.ts +13 -16
- package/src/wirings/workflow/graph/workflow-graph.types.ts +0 -5
- package/src/wirings/workflow/index.ts +14 -24
- package/src/wirings/workflow/pikku-scenario-service.ts +101 -27
- package/src/wirings/workflow/pikku-workflow-service.test.ts +15 -14
- package/src/wirings/workflow/pikku-workflow-service.ts +214 -743
- package/src/wirings/workflow/scenario-expectations.test.ts +75 -0
- package/src/wirings/workflow/scenario-hooks.test.ts +52 -0
- package/src/wirings/workflow/scenario-step.types.ts +8 -0
- package/src/wirings/workflow/workflow-approval-audit.ts +47 -0
- package/src/wirings/workflow/workflow-approval-policy.test.ts +524 -0
- package/src/wirings/workflow/workflow-approval-policy.ts +68 -0
- package/src/wirings/workflow/workflow-approval.ts +289 -0
- package/src/wirings/workflow/workflow-child-run-session.test.ts +89 -0
- package/src/wirings/workflow/workflow-constants.ts +47 -0
- package/src/wirings/workflow/workflow-dispatch-durability.test.ts +1 -1
- package/src/wirings/workflow/workflow-errors.ts +128 -0
- package/src/wirings/workflow/workflow-inline-authority.test.ts +12 -6
- package/src/wirings/workflow/workflow-meta-resolver.ts +45 -0
- package/src/wirings/workflow/workflow-missing-meta.test.ts +92 -0
- package/src/wirings/workflow/workflow-queue-routing.ts +85 -0
- package/src/wirings/workflow/workflow-queue-wiring.ts +149 -0
- package/src/wirings/workflow/workflow-recovery.ts +162 -0
- package/src/wirings/workflow/workflow-retry-policy.test.ts +1 -1
- package/src/wirings/workflow/workflow-run-authority.test.ts +212 -0
- package/src/wirings/workflow/workflow-run-engine.types.ts +91 -0
- package/src/wirings/workflow/workflow-run-ownership.ts +37 -0
- package/src/wirings/workflow/workflow-suspend.ts +61 -0
- package/src/wirings/workflow/workflow.types.ts +7 -9
- package/src/wirings-stay-decoupled.test.ts +127 -0
- package/tsconfig.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/internal.d.ts +0 -3
- package/dist/internal.js +0 -2
- package/dist/middleware/timeout.d.ts +0 -9
- package/dist/middleware/timeout.js +0 -15
- package/dist/pikku-response.d.ts +0 -6
- package/dist/pikku-response.js +0 -6
- package/dist/services/gopass-secrets.d.ts +0 -15
- package/dist/services/gopass-secrets.js +0 -76
- package/dist/services/http-scenario-actors.d.ts +0 -75
- package/dist/services/http-scenario-actors.js +0 -195
- package/dist/services/http-user-flow-actors.d.ts +0 -67
- package/dist/services/http-user-flow-actors.js +0 -193
- package/dist/services/scenario-actors-service.d.ts +0 -127
- package/dist/services/scenario-actors-service.js +0 -40
- package/dist/services/user-flow-actors-service.d.ts +0 -39
- package/dist/wirings/credential/wire-credential.d.ts +0 -48
- package/dist/wirings/credential/wire-credential.js +0 -47
- package/dist/wirings/oauth2/oauth2-client.d.ts +0 -47
- package/dist/wirings/oauth2/oauth2-client.js +0 -263
- package/dist/wirings/oauth2/oauth2-routes.d.ts +0 -35
- package/dist/wirings/oauth2/oauth2-routes.js +0 -146
- package/dist/wirings/scope/wire-scope.d.ts +0 -33
- package/dist/wirings/scope/wire-scope.js +0 -32
- package/dist/wirings/workflow/dsl/index.d.ts +0 -5
- package/dist/wirings/workflow/dsl/index.js +0 -4
- package/dist/wirings/workflow/graph/index.d.ts +0 -5
- package/dist/wirings/workflow/graph/index.js +0 -4
- package/src/internal.ts +0 -10
- /package/dist/{services/user-flow-actors-service.js → wirings/workflow/workflow-run-engine.types.js} +0 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A workflow's wire is built from the run record, not from the RPC service
|
|
4
|
+
description: The RPC service exposes no wire, so every rpcService.wire read was undefined; the run record is the only thing that carries the caller across a step boundary
|
|
5
|
+
tags: core, workflow
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A workflow's wire is built from the run record, not from the RPC service
|
|
9
|
+
|
|
10
|
+
`PikkuWorkflowService` builds a fresh wire each time it runs a workflow body or
|
|
11
|
+
starts a child. For a while it filled parts of that wire from the RPC service it
|
|
12
|
+
had been handed:
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
session: rpcService?.wire?.session,
|
|
16
|
+
rpc: rpcService?.wire?.rpc,
|
|
17
|
+
pikkuUserId: rpcService.wire?.pikkuUserId,
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`PikkuRPC` has no `wire`. Neither does the object `getContextRPCService`
|
|
21
|
+
actually returns — `ContextAwareRPCService` holds its wire *privately* and
|
|
22
|
+
exposes `invoke`, `remote`, `exposed`, `startWorkflow`, `agent` and
|
|
23
|
+
`rpcWithWire`, and nothing else. Every one of those reads was `undefined`, and
|
|
24
|
+
the `rpcService: any` parameter type is what kept the compiler quiet about it.
|
|
25
|
+
|
|
26
|
+
The consequence was silent and one-directional: a child workflow started from a
|
|
27
|
+
step never inherited the `pikkuUserId` its parent was running as, so a queued
|
|
28
|
+
child ran as nobody. Nothing failed — the field was simply absent, and the run
|
|
29
|
+
proceeded.
|
|
30
|
+
|
|
31
|
+
**The run record is the carrier.** `WorkflowRun.wire` is durable, is written
|
|
32
|
+
when the run is created, and survives the process boundary a queued step
|
|
33
|
+
crosses, which is exactly what a live service reference cannot do. Both the
|
|
34
|
+
run-body wire and the child-run wire now read `run.wire?.pikkuUserId`.
|
|
35
|
+
|
|
36
|
+
`session` and `rpc` are not copied at all. `runPikkuFunc` attaches `rpc` lazily
|
|
37
|
+
for the duration of an invocation and restores the previous descriptor
|
|
38
|
+
afterwards, and it resolves the session from the session store using
|
|
39
|
+
`pikkuUserId` — so both were being overwritten moments later anyway. The wire
|
|
40
|
+
these paths construct is a `PikkuRawWire` for that reason: it genuinely has no
|
|
41
|
+
`rpc` yet, and saying so is what let the dead reads be found.
|
|
42
|
+
|
|
43
|
+
**What this rules out:** reaching for the RPC service to answer "who is this
|
|
44
|
+
running as". It cannot answer, and the shape of the question hides that.
|
|
45
|
+
Anything a step needs to know about its caller has to be on the run.
|
package/knowledge/decisions/internals/addon-pikku-meta-ships-at-the-package-root-or-under-dist.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: An addon's .pikku meta ships at the package root or under dist, and readers probe both
|
|
4
|
+
description: The layout depends on the addon's build and files field, so a consumer resolving the directory by path must try `<pkg>/.pikku` and `<pkg>/dist/.pikku` before concluding an addon declares nothing
|
|
5
|
+
tags: services
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# An addon's .pikku meta ships at the package root or under dist, and readers probe both
|
|
9
|
+
|
|
10
|
+
An addon generates `.pikku/` at its project root, and whether that directory
|
|
11
|
+
reaches the published tarball at the root or inside `dist/` depends on the
|
|
12
|
+
addon's own `tsconfig` and `files` — both layouts are in the wild and both are
|
|
13
|
+
supported. The published `exports` map hides the difference for anyone
|
|
14
|
+
resolving by specifier: `"./.pikku/*"` points at wherever it landed, so
|
|
15
|
+
`require.resolve('<pkg>/.pikku/scopes/pikku-scopes-meta.gen.json')` works for
|
|
16
|
+
either shape, and that is the resolution every consumer should prefer.
|
|
17
|
+
|
|
18
|
+
Code that builds the path itself must probe both. `addonPikkuDir`
|
|
19
|
+
(`packages/addon/pikku-console/src/lib/derive-instance-overrides.ts`) tries
|
|
20
|
+
`<pkg>/.pikku` then `<pkg>/dist/.pikku` and returns `null` when neither exists.
|
|
21
|
+
The reason to say so out loud is the failure mode: a reader that assumes the
|
|
22
|
+
root finds nothing under a `dist`-shipping addon and cannot distinguish that
|
|
23
|
+
from an addon that declares no secrets, variables or scopes at all. Both answers
|
|
24
|
+
are "empty", and the wrong one is indistinguishable from a correct one until
|
|
25
|
+
something downstream reports an addon as ready when its secrets were never
|
|
26
|
+
checked.
|
|
27
|
+
|
|
28
|
+
**What this rules out:** hardcoding either path in a consumer, and treating a
|
|
29
|
+
missing directory as evidence an addon declares nothing without having tried
|
|
30
|
+
both. It also rules out normalising the layout by making the CLI move the
|
|
31
|
+
directory at publish time — the export map already makes the layout private to
|
|
32
|
+
the package, and the addons shipping each shape are already published.
|
package/knowledge/decisions/internals/agent-context-waits-for-a-tool-result-still-being-written.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: Loading agent context waits for a tool result that may still be landing
|
|
4
|
+
description: An interrupted run's tool can still be writing to the thread, and in voice the next turn arrives within seconds — soon enough to load context missing what it is about to be asked about
|
|
5
|
+
tags: core, ai-agent
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Loading agent context waits for a tool result that may still be landing
|
|
9
|
+
|
|
10
|
+
Before a turn loads its thread, it settles any tool whose run was interrupted
|
|
11
|
+
but whose result is still being written to that thread.
|
|
12
|
+
|
|
13
|
+
The window is small and, on a typed interface, usually irrelevant — a person
|
|
14
|
+
takes seconds to write the next message. In voice it is not: the next turn lands
|
|
15
|
+
within a second or two of the last one. Without the wait, the model loads a
|
|
16
|
+
thread that is missing the very result the user is about to ask about, and
|
|
17
|
+
answers as though the tool never ran.
|
|
18
|
+
|
|
19
|
+
**What this rules out:** treating the settle as belt-and-braces and dropping it
|
|
20
|
+
to save a round trip. The failure it prevents is a confidently wrong answer, not
|
|
21
|
+
an error, and it only appears on the fastest transport.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: Agent speech travels as a CUSTOM AG-UI event rather than being dropped
|
|
4
|
+
description: AG-UI has no speech event, and dropping it makes a voice agent reached over HTTP silently inaudible while the provider still bills for the audio
|
|
5
|
+
tags: core, ai-agent
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Agent speech travels as a CUSTOM AG-UI event
|
|
9
|
+
|
|
10
|
+
The AG-UI protocol has no event type for synthesized speech, so the bridge
|
|
11
|
+
forwards it as `CUSTOM`, the same way it forwards the other pikku-specific
|
|
12
|
+
events.
|
|
13
|
+
|
|
14
|
+
The alternative was tried: the mapper dropped the event, on the reasoning that a
|
|
15
|
+
protocol without a speech event has no way to carry one. The result is a voice
|
|
16
|
+
agent reached over HTTP that is completely silent — `voiceOutput` synthesizes
|
|
17
|
+
every sentence, the provider bills for every one of them, and none of it gets
|
|
18
|
+
past the mapper. Nothing errors, so there is nothing to find.
|
|
19
|
+
|
|
20
|
+
**What this rules out:** filtering unknown event types at the AG-UI boundary as
|
|
21
|
+
a tidiness measure. `CUSTOM` exists precisely so a protocol gap degrades to
|
|
22
|
+
"the client ignores it" rather than "the server threw the work away".
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: An addon's scope root loses to a root the host app already declares
|
|
4
|
+
description: loadAddonFunctionsMeta merges addon scope trees by root name, first declaration wins, so an addon sharing a root with its host contributes nothing — silently
|
|
5
|
+
tags: services
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# An addon's scope root loses to a root the host app already declares
|
|
9
|
+
|
|
10
|
+
`loadAddonFunctionsMeta`
|
|
11
|
+
(`packages/inspector/src/utils/load-addon-functions-meta.ts`) reads each wired
|
|
12
|
+
addon's `pikku-scopes-meta.gen.json` and pushes its entries into
|
|
13
|
+
`state.scopes.definitions`, skipping any whose root name is already present. The
|
|
14
|
+
host app's own declarations were inspected first, so the host always wins.
|
|
15
|
+
|
|
16
|
+
The skip is per **root**, not per node. An addon declaring `admin.console.*` in
|
|
17
|
+
an app that declares `admin` contributes none of it — not the root it shares,
|
|
18
|
+
and not the branch the app never declared. Nothing errors: the scopes simply do
|
|
19
|
+
not exist, so `ScopeId` never gains them, no role can be granted one, and every
|
|
20
|
+
addon function requiring one denies everybody. The failure surfaces as a
|
|
21
|
+
`MissingScopeError` against a scope that cannot be granted, which reads as a
|
|
22
|
+
permissions bug rather than a merge that dropped.
|
|
23
|
+
|
|
24
|
+
This is a different rule from
|
|
25
|
+
[scope roots may be co-declared by an addon and its host
|
|
26
|
+
app](scope-roots-may-be-co-declared-by-an-addon-and-its-host-app.md): that one
|
|
27
|
+
is about `flattenScopeDefinitions` deduping ids *within* a build where both
|
|
28
|
+
declarations are present and identical. This one is about the addon's copy never
|
|
29
|
+
arriving. The consequence for addon authors is the same either way — own a root
|
|
30
|
+
outright, named for the package or vendor, and nest everything under it. It is
|
|
31
|
+
why `@pikku/addon-console` declares `pikku:console:*` rather than
|
|
32
|
+
`admin:console:*`.
|
|
33
|
+
|
|
34
|
+
**What this rules out:** shipping an addon whose scopes hang off a root a host
|
|
35
|
+
is likely to declare, and deep-merging the two trees here so both survive. A
|
|
36
|
+
merge would have to reconcile conflicting descriptions and display names for the
|
|
37
|
+
shared nodes, and would let an addon graft capabilities onto the host's `admin`
|
|
38
|
+
tree — where a role granting `admin` would pick them up without anyone having
|
|
39
|
+
asked for them.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: An interrupt is not a failure, and the non-streaming path throws rather than returning
|
|
4
|
+
description: It skips the onError hooks and never becomes an errorMessage; with no partial reply to hand back, a typed throw is what distinguishes it from a provider outage
|
|
5
|
+
tags: core, ai-agent
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# An interrupt is not a failure
|
|
9
|
+
|
|
10
|
+
Interrupting a run skips the `onError` hooks entirely and never becomes an
|
|
11
|
+
`errorMessage`. Someone asked the agent to stop and it stopped; that is the
|
|
12
|
+
feature working, and running failure handlers over it would report an incident
|
|
13
|
+
that did not happen.
|
|
14
|
+
|
|
15
|
+
The streaming and non-streaming paths then diverge, because what they can hand
|
|
16
|
+
back differs. A stream has already delivered part of the reply, so it returns
|
|
17
|
+
that fragment. `runAIAgent` has delivered nothing — there is no partial answer to
|
|
18
|
+
return — so it throws a typed error instead. A caller can tell that apart from a
|
|
19
|
+
provider outage, which returning an empty result would not allow.
|
|
20
|
+
|
|
21
|
+
**What this rules out:** unifying the two paths on "return whatever you have".
|
|
22
|
+
For the non-streaming path that is an empty string, and an empty string is
|
|
23
|
+
indistinguishable from a model that answered with nothing.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: An interrupt for a run owned by another instance says so, rather than returning false
|
|
4
|
+
description: A bare false is indistinguishable from "already finished", which is the one deployment shape the in-process registry cannot cover
|
|
5
|
+
tags: core, ai-agent
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# An interrupt for a run owned by another instance says so
|
|
9
|
+
|
|
10
|
+
`interruptAIAgent` resolves a run through `aiRunState`, then tries to abort it
|
|
11
|
+
via the in-process registry. A run still marked `running` that this process has
|
|
12
|
+
no abort handle for is executing on another instance.
|
|
13
|
+
|
|
14
|
+
Returning a bare `false` there is indistinguishable from "the run already
|
|
15
|
+
finished" — the ordinary, uninteresting outcome. So the single deployment shape
|
|
16
|
+
the in-process registry does not cover, multi-instance, fails silently: an agent
|
|
17
|
+
that will not stop talking, and nothing in the logs to say why. The call reports
|
|
18
|
+
the condition instead.
|
|
19
|
+
|
|
20
|
+
**What this rules out:** collapsing the two outcomes into one boolean because
|
|
21
|
+
the caller "only cares whether it stopped". The caller cares a great deal about
|
|
22
|
+
the difference between *stopped* and *cannot be stopped from here*.
|
|
23
|
+
|
|
24
|
+
The fix for the underlying gap is `signalRunInterrupt`, which fans the interrupt
|
|
25
|
+
out over `eventHub` so every instance tries locally.
|
package/knowledge/decisions/internals/an-agent-stream-send-must-return-the-inner-sends-promise.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A wrapped agent-stream send must return the inner send's promise
|
|
4
|
+
description: Middleware runs asynchronously, so dropping the returned promise turns every awaited channel.send upstream into a no-op
|
|
5
|
+
tags: core, ai-agent
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A wrapped agent-stream send must return the inner send's promise
|
|
9
|
+
|
|
10
|
+
`streamAIAgent` wraps the caller's channel so every event passes through the
|
|
11
|
+
stream middleware. That wrapper's `send` returns whatever the inner `send`
|
|
12
|
+
returns, and it must: the middleware chain is asynchronous, so a wrapper that
|
|
13
|
+
calls the inner `send` and returns `undefined` resolves immediately while the
|
|
14
|
+
event is still in flight.
|
|
15
|
+
|
|
16
|
+
Every `await channel.send(...)` upstream then becomes a no-op that resolves
|
|
17
|
+
before the thing it is waiting for has happened. The one that matters is the
|
|
18
|
+
final flush — a buffering hook such as `voiceOutput` is still synthesizing audio
|
|
19
|
+
when the awaited send resolves, and the `close()` that follows discards it.
|
|
20
|
+
|
|
21
|
+
**What this rules out:** writing the wrapper as a fire-and-forget `(msg) => {
|
|
22
|
+
inner.send(msg) }`, which reads as equivalent and is not.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A message with nothing to say carries no text part at all
|
|
4
|
+
description: An attachment on its own is a real turn, and providers are entitled to reject an empty text part sitting beside it
|
|
5
|
+
tags: core, ai-agent
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A message with nothing to say carries no text part
|
|
9
|
+
|
|
10
|
+
When a turn has no text, the text part is omitted rather than included as an
|
|
11
|
+
empty string.
|
|
12
|
+
|
|
13
|
+
An attachment on its own is a legitimate turn — a spoken one carries audio and
|
|
14
|
+
no text whatsoever — so "no text" is a normal state, not a degenerate one.
|
|
15
|
+
Providers are entitled to reject a message part with empty content, and that
|
|
16
|
+
rejection would land on a caller who never wrote any text to begin with.
|
|
17
|
+
|
|
18
|
+
**What this rules out:** normalising the text to `''` for a uniform message
|
|
19
|
+
shape. Uniformity here buys nothing and costs a provider error on the one turn
|
|
20
|
+
type that most needs to work.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A transcript is recorded only when something was actually heard
|
|
4
|
+
description: Recording an empty string sends a transcript event saying the user said nothing, which renders as an empty bubble rather than a pending one
|
|
5
|
+
tags: core, ai-agent
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A transcript is recorded only when something was actually heard
|
|
9
|
+
|
|
10
|
+
A turn can carry audio that reads entirely as non-speech and still have content
|
|
11
|
+
— an image with a silent caption clip — so there is nothing above to throw.
|
|
12
|
+
`voiceInput` records the transcript only when speech was found.
|
|
13
|
+
|
|
14
|
+
Writing `''` instead would emit a transcript event asserting that the user said
|
|
15
|
+
nothing. A client that distinguishes "not transcribed yet" from "transcribed"
|
|
16
|
+
by whether the key is present would then render that turn as a permanently
|
|
17
|
+
empty bubble rather than a pending one — a worse outcome than showing nothing,
|
|
18
|
+
because it looks settled.
|
|
19
|
+
|
|
20
|
+
**What this rules out:** defaulting the transcript to an empty string for a
|
|
21
|
+
uniform event shape. Absence and emptiness mean different things to the client,
|
|
22
|
+
and only absence is recoverable.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: An unref'd timer cannot be awaited under node:test
|
|
4
|
+
description: The channel RPC registry unrefs its timeout so a pending call never holds a process open, which makes that timeout unawaitable in a test and silently cancels the rest of the file
|
|
5
|
+
tags: core, channel, testing
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# An unref'd timer cannot be awaited under node:test
|
|
9
|
+
|
|
10
|
+
`ChannelRPCRegistry.register()` schedules the call's timeout and then unrefs it:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
// Never hold the process open waiting on a peer that may not answer.
|
|
14
|
+
timer?.unref?.()
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
That is correct, and should stay. A server shutting down must not be held open
|
|
18
|
+
for up to 30 seconds per in-flight reverse RPC.
|
|
19
|
+
|
|
20
|
+
It also means the timeout can never fire in a test that awaits it. An unref'd
|
|
21
|
+
timer does not keep the event loop alive, so when the pending call is the only
|
|
22
|
+
thing left the loop drains first, and node:test reports:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
Promise resolution is still pending but the event loop has already resolved
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The damage is not local. That one test hangs, the runner cancels the **file**,
|
|
29
|
+
and every later test in it is reported `cancelledByParent` — 32 of them in
|
|
30
|
+
`channel-rpc.test.ts`, of which only two were the actual problem. Worse, the
|
|
31
|
+
count moves: under a full-suite run other ref'd handles sometimes keep the loop
|
|
32
|
+
alive long enough for the timer to land, so the file reported `32 cancelled` or
|
|
33
|
+
`1 fail + 31 cancelled` depending on timing. Two of the "failures" were tests
|
|
34
|
+
that pass perfectly well on their own.
|
|
35
|
+
|
|
36
|
+
Reproducible with no project code at all — a bare `node --test` file where one
|
|
37
|
+
test awaits a ref'd timer and one awaits an unref'd one passes the first and
|
|
38
|
+
cancels the second.
|
|
39
|
+
|
|
40
|
+
**The fix belongs in the test, not the registry.** Use the test-scoped mock
|
|
41
|
+
clock and drive it by hand:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
test('times out instead of hanging forever', async (t) => {
|
|
45
|
+
t.mock.timers.enable({ apis: ['setTimeout'] })
|
|
46
|
+
const registry = new ChannelRPCRegistry(10)
|
|
47
|
+
const call = registry.register()
|
|
48
|
+
|
|
49
|
+
const rejected = assert.rejects(call.promise, ...) // attach BEFORE ticking
|
|
50
|
+
t.mock.timers.tick(10)
|
|
51
|
+
await rejected
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`t.mock.timers` is scoped to the test and resets itself, so tests that want a
|
|
56
|
+
real clock are unaffected. Attach the rejection handler before ticking, or the
|
|
57
|
+
rejection lands unhandled.
|
|
58
|
+
|
|
59
|
+
**What this rules out:** dropping the `unref` to make the tests pass. That
|
|
60
|
+
trades a test-only annoyance for a production one — every in-flight call would
|
|
61
|
+
then pin the process during shutdown. Also rules out reading a cancelled count
|
|
62
|
+
in this file as a count of broken tests; check which test actually hangs first,
|
|
63
|
+
because the rest are collateral.
|
|
64
|
+
|
|
65
|
+
Related: [[the-api-report-pins-members-not-just-names]] for the other case where
|
|
66
|
+
a measurement in this repo counted the wrong thing convincingly.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: `getState<T>()` lets the caller name any type, so every implementation asserts
|
|
4
|
+
description: The type parameter appears only in the return position with nothing to infer from, so it is a caller-chosen claim rather than a checked fact
|
|
5
|
+
tags: core, channel
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# `getState<T>()` lets the caller name any type, so every implementation asserts
|
|
9
|
+
|
|
10
|
+
`PikkuChannel` declares its per-connection scratch state as:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
setState<T>(state: T): Promise<void> | void
|
|
14
|
+
getState<T>(): Promise<T | undefined> | T | undefined
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`T` on `getState` appears only in the return position, with no parameter to
|
|
18
|
+
infer it from. So `T` is whatever the caller writes, and the call site gets that
|
|
19
|
+
type back with nothing having checked it. `setState<T>` is separately generic,
|
|
20
|
+
so the two are not even tied to each other — a channel can be written with one
|
|
21
|
+
shape and read as another and neither call complains.
|
|
22
|
+
|
|
23
|
+
Every implementation therefore holds its state in a concrete variable and
|
|
24
|
+
asserts on the way out. The SSE channel in `http-runner.ts` and the CLI channel
|
|
25
|
+
in `cli-runner.ts` both do exactly that, and the assertion is unavoidable: no
|
|
26
|
+
concrete value is assignable to a type the caller has not yet chosen.
|
|
27
|
+
|
|
28
|
+
**What this rules out:** deleting the assertions in the implementations — they
|
|
29
|
+
are forced by the signature, not by sloppiness. Fixing this properly means
|
|
30
|
+
making the channel generic in its state type (`PikkuChannel<Opening, Out,
|
|
31
|
+
Remote, State>`) so `setState` and `getState` agree and inference has something
|
|
32
|
+
to work from, which changes the type of every channel in every app. Until that
|
|
33
|
+
is worth doing, the assertions stay and this note explains why they exist.
|
package/knowledge/decisions/internals/gateway-listener-middleware-runs-without-an-rpc-on-the-wire.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: Gateway middleware sees wire.rpc on webhook and websocket, but not on listener
|
|
4
|
+
description: The listener transport invokes its handler directly, so middleware runs outside any runPikkuFunc call and the lazily-attached rpc is absent
|
|
5
|
+
tags: core, gateway
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Gateway middleware sees `wire.rpc` on webhook and websocket, but not on listener
|
|
9
|
+
|
|
10
|
+
`runPikkuFunc` in `packages/core/src/function/function-runner.ts` attaches `rpc`
|
|
11
|
+
to the invocation wire with `Object.defineProperty`, as a lazy getter that
|
|
12
|
+
replaces itself on first read — and restores the previous descriptor in its
|
|
13
|
+
`finally`. `rpc` therefore exists on a wire *only for the duration of a function
|
|
14
|
+
invocation*. That is why `PikkuRawWire` is `Omit<PikkuWire, 'rpc'>`: it is the
|
|
15
|
+
wire as a runner constructs it, before the function runner adds `rpc`.
|
|
16
|
+
|
|
17
|
+
The three gateway transports reach middleware differently:
|
|
18
|
+
|
|
19
|
+
- **webhook** and **websocket** register their handlers with `addFunction`, so
|
|
20
|
+
the handler body already runs inside a `runPikkuFunc` invocation. The `wire`
|
|
21
|
+
it receives is a full `PikkuWire`, and `config.middleware` runs inside that
|
|
22
|
+
same invocation — `wire.rpc` is live.
|
|
23
|
+
- **listener** has no such wrapper. `createListenerMessageHandler` is handed
|
|
24
|
+
straight to `adapter.init()` by the `GatewayService`, builds
|
|
25
|
+
`const wire: PikkuRawWire = {}` itself, and runs `config.middleware` *before*
|
|
26
|
+
`invoke()` reaches `runPikkuFunc`. There is no invocation in progress, so
|
|
27
|
+
`wire.rpc` is `undefined`.
|
|
28
|
+
|
|
29
|
+
A gateway middleware that calls `wire.rpc.invoke(...)` therefore works on two
|
|
30
|
+
transports and throws on the third, with nothing in the types to say so — the
|
|
31
|
+
call into `runMiddleware` needs an assertion, because `runMiddleware` is typed
|
|
32
|
+
off `CorePikkuMiddleware`, whose wire parameter is `PikkuWire`.
|
|
33
|
+
|
|
34
|
+
**What this rules out:** treating the assertion at that call as noise to be
|
|
35
|
+
deleted. It is naming a real gap. It also rules out "just widen
|
|
36
|
+
`CorePikkuMiddleware` to accept `PikkuRawWire`" as a free fix — that would make
|
|
37
|
+
`rpc` optional for *every* middleware in the framework, pushing the problem onto
|
|
38
|
+
every consumer to satisfy one transport.
|
|
39
|
+
|
|
40
|
+
**Still open:** whether the listener path should wrap its handler in a
|
|
41
|
+
`runPikkuFunc` invocation the way the other two do, which would make the three
|
|
42
|
+
transports behave identically and remove the assertion. Nobody has argued
|
|
43
|
+
against it; it simply has not been done.
|
package/knowledge/decisions/internals/hot-reload-writes-into-the-function-map-captured-at-startup.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: Hot reload writes into the function map captured at startup, not pikkuState's current one
|
|
4
|
+
description: A dev-server watcher may have swapped in a codegen-scoped map whose writes are discarded on restore, and schemas are deliberately left alone
|
|
5
|
+
tags: core, dev
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Hot reload writes into the function map captured at startup
|
|
9
|
+
|
|
10
|
+
`reloadGeneratedMeta` registers every function-config export it finds, replacing
|
|
11
|
+
known functions and adding new ones. It writes into the map captured when the
|
|
12
|
+
reloader started, **not** into whatever `pikkuState` currently returns.
|
|
13
|
+
|
|
14
|
+
A dev-server watcher may have temporarily swapped in a codegen-scoped map while
|
|
15
|
+
regenerating. Writes into that map are discarded when it restores the real one,
|
|
16
|
+
so a reload that used the current map would appear to succeed and silently lose
|
|
17
|
+
every function it registered.
|
|
18
|
+
|
|
19
|
+
Schemas are deliberately not touched on this path. A function config's
|
|
20
|
+
`input`/`output` hold raw schema objects as the author wrote them, while the
|
|
21
|
+
schema map carries the JSON Schema that codegen produced. They are different
|
|
22
|
+
representations of the same thing, and mixing them crashed reloads.
|
|
23
|
+
|
|
24
|
+
**What this rules out:** "simplifying" the reloader to call `pikkuState(...)` at
|
|
25
|
+
write time, and extending it to keep `input`/`output` and the schema map in sync
|
|
26
|
+
without first converting between the two representations.
|
|
@@ -10,13 +10,23 @@ A rule about how core behaves: what runs where, in what order, and what a
|
|
|
10
10
|
caller is entitled to assume.
|
|
11
11
|
|
|
12
12
|
<!-- pikku:knowledge-index -->
|
|
13
|
+
- [A non-streaming agent run registers with aiRunState on the same terms as a streaming one](a-non-streaming-agent-run-registers-with-airunstate-too.md) — Otherwise interruptAIAgent finds the run, passes the ownership check, then cannot stop it — and reports that as if the run were on another host
|
|
14
|
+
- [A resumed turn is as interruptible as the first one](a-resumed-agent-turn-is-as-interruptible-as-the-first.md) — It is the same person listening to the same voice, and after an approval it is where most of the reply actually gets spoken
|
|
15
|
+
- [A scenario step's prose template is offered to a virtual user unfilled](a-scenario-step-template-is-offered-unfilled.md) — A reporter fills placeholders from a run that happened; there is no run yet, and the filled form would answer the question the user is there to answer
|
|
13
16
|
- [A secret that fails to decrypt fails the whole read](a-secret-that-fails-to-decrypt-fails-the-whole-read.md) — getSecrets throws naming the key and its key_version rather than omitting the row, because a silent omission surfaces as an unrelated failure much later
|
|
17
|
+
- [A virtual user decides whether to trust its notes once per turn, by one roll](a-virtual-user-decides-whether-to-trust-memory-once-per-turn.md) — The difference between the stale, newcomer and auditor dispositions is expressed as a single probability rather than as prose in each prompt
|
|
18
|
+
- [A virtual user run is not a workflow and not a queued job](a-virtual-user-run-is-not-a-workflow-and-not-a-queued-job.md) — runVirtualUser writes its record, dispatches the run without awaiting it, and returns the id — because an exploratory run has nothing to replay and the record already carries what a queue would be holding
|
|
19
|
+
- [A wall-clock threshold is a load test in disguise](a-wall-clock-threshold-is-a-load-test-in-disguise.md) — The KEK derivation test asserted a fixed 50ms budget for work that took 10ms, which went red about one run in five once the suite was large enough to compete for the machine
|
|
20
|
+
- [A workflow's wire is built from the run record, not from the RPC service](a-workflow-wire-is-built-from-the-run-not-from-the-rpc-service.md) — The RPC service exposes no wire, so every rpcService.wire read was undefined; the run record is the only thing that carries the caller across a step boundary
|
|
14
21
|
- [An actor conversation starts from a seeded kickoff message](actor-flow-conversations-seed-a-hidden-kickoff-message.md) — The actor's first turn needs a non-empty message list because providers reject an empty prompt; the seed is an instruction and stays out of the transcript
|
|
15
22
|
- [The actor-flow conversation engine only sees a transport-agnostic target driver](actor-flow-drives-the-target-through-a-transport-seam.md) — The engine never imports the agent runner; the target is injected as run/approve, so scenarios exercise the real wire path
|
|
16
23
|
- [An actor-flow verdict is the persona's self-evaluation, not an assertion](actor-flow-verdicts-are-llm-self-evaluations.md) — The engine returns what the actor judged plus the transcript; deterministic checks stay with the caller
|
|
17
24
|
- [Addon package roots resolve by walking node module search paths](addon-package-roots-resolve-by-walking-node-module-search-paths.md) — LocalMetaService finds an addon's directory by scanning resolve.paths, not by require.resolve, because addon packages expose no main entry
|
|
25
|
+
- [An addon's .pikku meta ships at the package root or under dist, and readers probe both](addon-pikku-meta-ships-at-the-package-root-or-under-dist.md) — The layout depends on the addon's build and files field, so a consumer resolving the directory by path must try `<pkg>/.pikku` and `<pkg>/dist/.pikku` before concluding an addon declares nothing
|
|
18
26
|
- [Addon singleton services are cached per namespace, not per package](addon-singleton-services-are-cached-per-namespace-not-per-package.md) — Each wireAddon instance gets its own services built from its own overrides, at the cost of one service graph per wired instance
|
|
19
27
|
- [Bare workflow names from inside an addon are prefixed with the consumer's namespace](addon-workflow-names-are-prefixed-with-the-consumer-namespace.md) — The addon's workflowService is proxied so an addon never has to hardcode the name its consumer chose
|
|
28
|
+
- [Loading agent context waits for a tool result that may still be landing](agent-context-waits-for-a-tool-result-still-being-written.md) — An interrupted run's tool can still be writing to the thread, and in voice the next turn arrives within seconds — soon enough to load context missing what it is about to be asked about
|
|
29
|
+
- [Agent speech travels as a CUSTOM AG-UI event rather than being dropped](agent-speech-travels-as-a-custom-agui-event.md) — AG-UI has no speech event, and dropping it makes a voice agent reached over HTTP silently inaudible while the provider still bills for the audio
|
|
20
30
|
- [The AG-UI bridge obeys the client's event-ordering contract exactly](ai-agent-agui-bridge-obeys-the-client-ordering-contract.md) — RUN_STARTED opens lazily, RUN_FINISHED fires once on done, and step names are globally sequential — a violation makes the client drop the whole stream
|
|
21
31
|
- [Audio chunks are labelled with the format the provider actually returned](ai-agent-audio-chunks-carry-the-format-the-provider-returned.md) — The configured format is only a request, so the response's own format wins with the request and pcm16 as fallbacks
|
|
22
32
|
- [A credential-required tool result never reaches the client or the transcript](ai-agent-credential-suspensions-hide-the-tool-result.md) — The run suspends with credential-request events instead, leaving the tool call unresulted so it can be resumed after connecting
|
|
@@ -30,8 +40,16 @@ caller is entitled to assume.
|
|
|
30
40
|
- [A tool's execute() failure is logged before the AI SDK swallows it](ai-agent-tool-execute-failures-are-logged-unconditionally.md) — Every agent tool is wrapped in a logging try/catch, because a thrown tool error otherwise becomes a conversational reply and is invisible server-side
|
|
31
41
|
- [Voice input transcribes audio parts sequentially and in place](ai-agent-voice-input-transcribes-audio-parts-in-place.md) — Each audio part is replaced by its text where it sat, one at a time, bounding concurrent downloads and preserving content order
|
|
32
42
|
- [Working memory is persisted only when the merged value validates](ai-agent-working-memory-is-persisted-only-when-valid.md) — A failed schema check logs and drops the update rather than saving it, because invalid state poisons every later read
|
|
43
|
+
- [An addon's scope root loses to a root the host app already declares](an-addon-scope-root-loses-to-a-root-the-host-already-declares.md) — loadAddonFunctionsMeta merges addon scope trees by root name, first declaration wins, so an addon sharing a root with its host contributes nothing — silently
|
|
44
|
+
- [An interrupt is not a failure, and the non-streaming path throws rather than returning](an-agent-interrupt-is-not-a-failure.md) — It skips the onError hooks and never becomes an errorMessage; with no partial reply to hand back, a typed throw is what distinguishes it from a provider outage
|
|
45
|
+
- [An interrupt for a run owned by another instance says so, rather than returning false](an-agent-run-owned-by-another-instance-says-so.md) — A bare false is indistinguishable from "already finished", which is the one deployment shape the in-process registry cannot cover
|
|
46
|
+
- [A wrapped agent-stream send must return the inner send's promise](an-agent-stream-send-must-return-the-inner-sends-promise.md) — Middleware runs asynchronously, so dropping the returned promise turns every awaited channel.send upstream into a no-op
|
|
47
|
+
- [A message with nothing to say carries no text part at all](an-empty-text-part-is-omitted-from-an-agent-message.md) — An attachment on its own is a real turn, and providers are entitled to reject an empty text part sitting beside it
|
|
48
|
+
- [A transcript is recorded only when something was actually heard](an-empty-transcript-is-not-recorded.md) — Recording an empty string sends a transcript event saying the user said nothing, which renders as an empty bubble rather than a pending one
|
|
49
|
+
- [An unref'd timer cannot be awaited under node:test](an-unref-d-timer-cannot-be-awaited-under-node-test.md) — The channel RPC registry unrefs its timeout so a pending call never holds a process open, which makes that timeout unawaitable in a test and silently cancels the rest of the file
|
|
33
50
|
- [Channel message handlers accept three config shapes](channel-message-handlers-accept-three-config-shapes.md) — onMessage may be a function config, a wrapper with middleware, or a wrapper around a function config — the runtime discriminates structurally
|
|
34
51
|
- [Channel middleware caching covers only statically resolved middleware](channel-middleware-caches-only-statically-resolved-middleware.md) — Inherited tag/named middleware is cached per uid; per-run closures are appended fresh every call, at the cost of re-allocating the array
|
|
52
|
+
- [`getState<T>()` lets the caller name any type, so every implementation asserts](channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md) — The type parameter appears only in the return position with nothing to infer from, so it is a caller-chosen claim rather than a checked fact
|
|
35
53
|
- [Channel state is per-socket, session state is per-user](channel-state-is-per-socket-session-state-is-per-user.md) — ChannelStore holds connection-scoped scratch data keyed by channelId, deliberately separate from the pikkuUserId-keyed SessionStore
|
|
36
54
|
- [A channel's user id is persisted only after onConnect middleware has run](channel-user-id-is-persisted-after-onconnect-middleware-runs.md) — The channelId to pikkuUserId mapping is written post-onConnect, because auth middleware is what establishes the session
|
|
37
55
|
- [CLI option names are camelCase in state and kebab-case on the command line](cli-option-names-are-camelcase-in-state-and-kebab-on-the-command-line.md) — Option keys match the function's input field names so they can be plucked by schema, and are converted to kebab only for display and parsing
|
|
@@ -49,9 +67,11 @@ caller is entitled to assume.
|
|
|
49
67
|
- [Pikku state is a global map written only at registration time](core-state-is-a-global-map-written-only-at-registration-time.md) — A symbol-keyed globalThis map holds the wiring registry; nothing per-request may ever be written to it
|
|
50
68
|
- [Email meta is read uncached because codegen rewrites it mid-session](email-meta-is-read-uncached-because-codegen-rewrites-it-mid-session.md) — getEmailMeta re-reads its file on every call, unlike every other meta accessor, because the file appears and changes during a long-lived session
|
|
51
69
|
- [Gateway adapters resolve lazily and are promise-cached](gateway-adapters-resolve-lazily-and-are-promise-cached.md) — wireGateway accepts an adapter factory because real adapters need boot-time secrets, which forces the webhook GET route to be registered unconditionally
|
|
70
|
+
- [Gateway middleware sees wire.rpc on webhook and websocket, but not on listener](gateway-listener-middleware-runs-without-an-rpc-on-the-wire.md) — The listener transport invokes its handler directly, so middleware runs outside any runPikkuFunc call and the lazily-attached rpc is absent
|
|
52
71
|
- [Gateway webhook challenges echo bytes not JSON](gateway-webhook-challenges-echo-bytes-not-json.md) — String verification challenges are returned raw with returnsJSON false, because platforms byte-compare the echo and JSON quoting fails the handshake
|
|
53
72
|
- [Gateway wiring is a meta-wiring over HTTP and channels](gateway-wiring-is-a-meta-wiring-over-http-and-channels.md) — wireGateway writes handler implementations into the HTTP and channel state directly while the inspector compiles the corresponding meta, so runtime registration deliberately writes no meta
|
|
54
73
|
- [Generated src paths in pikku meta are absolute](generated-src-paths-in-pikku-meta-are-absolute.md) — emailsMeta.src is resolved by the CLI at generation time, so reading through the project-relative helpers produces a wrong compound path
|
|
74
|
+
- [Hot reload writes into the function map captured at startup, not pikkuState's current one](hot-reload-writes-into-the-function-map-captured-at-startup.md) — A dev-server watcher may have swapped in a codegen-scoped map whose writes are discarded on restore, and schemas are deliberately left alone
|
|
55
75
|
- [HTTP request bodies are read once and shared between consumers](http-request-bodies-are-read-once-and-shared.md) — The fetch request wrapper memoises the single-use body and builds web Requests lazily, at the cost of holding the whole body in memory
|
|
56
76
|
- [HTTP route groups cascade config in a fixed, per-field order](http-route-groups-cascade-config-in-a-fixed-order.md) — basePath concatenates, tags and middleware merge outward-in, auth is overridden by the innermost group
|
|
57
77
|
- [The HTTP router matches normalized paths but returns the registered path](http-router-matches-normalized-paths-but-returns-registered-ones.md) — Matching normalizes the leading slash while the match result carries the original key, because pikkuState is keyed by the registered string
|
|
@@ -64,28 +84,47 @@ caller is entitled to assume.
|
|
|
64
84
|
- [Istanbul statement counts attach to the start line only](istanbul-statement-counts-attach-to-the-start-line-only.md) — The istanbul coverage reader credits a statement's hits to its first line, so an enclosing multi-line statement cannot mask an unexecuted inner one
|
|
65
85
|
- [Local trigger and gateway services assume a single process](local-trigger-and-gateway-services-assume-a-single-process.md) — InMemoryTriggerService and LocalGatewayService start every listener unconditionally with no distributed claiming, so a second instance duplicates every event
|
|
66
86
|
- [Node-only builtins are imported dynamically](node-only-builtins-are-imported-dynamically.md) — V8CoverageService imports node:inspector inside start() so the module stays loadable on runtimes that have no such builtin
|
|
87
|
+
- [One project-shape check, called by both validators](one-project-shape-check-two-validators.md) — workspace validate and fabric validate were separate walks over the same project that duplicated sixteen findings verbatim; the shared half now lives in shared-checks.ts and fabric validate is that plus the deploy-shaped checks
|
|
88
|
+
- [Only functions marked `expose: true` enter a virtual user's catalogue](only-exposed-functions-enter-a-virtual-user-catalogue.md) — Absent is not permissive — an unexposed function 404s over rpc, so offering one spends a step to learn nothing about the product
|
|
67
89
|
- [Queue group concurrency keeps one shared queue fair](queue-group-concurrency-keeps-one-shared-queue-fair.md) — Per-group slot caps let many producers share a single queue instead of one queue each, at the cost of a cap that must stay under batchSize
|
|
68
90
|
- [Queue jobs always carry an explicit attempts count](queue-jobs-always-carry-an-explicit-attempts-count.md) — resolveJobOptions always passes attempts so a queue backend can never apply its own retry default, and an explicit retries of 0 is honoured
|
|
69
91
|
- [Remote addons dispatch over HTTP instead of through local function meta](remote-addons-dispatch-over-http-instead-of-local-meta.md) — wireRemoteAddon ships the addon as a devDependency and posts to the host, so there is deliberately no local handler to resolve
|
|
70
92
|
- [Bare RPC names resolve through the caller's package scope before root](rpc-names-resolve-through-package-scope-before-root.md) — An addon's own functions win over root RPC meta for bare names, and the resolving scope is returned so it can be threaded into runPikkuFunc
|
|
71
93
|
- [Scenario agent calls sign in on 401 only](scenario-agent-calls-sign-in-on-401-only.md) — postAgent sends the first request with whatever session it holds and logs in only if refused, so an actor can converse with a no-auth agent with no sign-in wiring
|
|
94
|
+
- [`given` and `when` are sugar for each other; `then` is not](scenario-given-and-when-are-sugar-but-then-is-not.md) — The phase decides whether a step's bindings are alternatives or witnesses, so the same step function called as `when` and as `then` runs differently
|
|
72
95
|
- [Scenario meta lives apart from app meta but merges when read off disk](scenario-meta-lives-apart-from-app-meta-but-merges-when-read-off-disk.md) — Scenarios generate into .pikku/scenarios so no app module imports them, yet MetaService folds them back into the workflow and function meta
|
|
73
96
|
- [Scenario steps return drained response records](scenario-steps-return-drained-response-records.md) — A scenario step hands back ScenarioHttpResponse rather than a Response, because the body stream reads once and the return value crosses into the run record
|
|
97
|
+
- [Scenarios, steps and personas live in files named for them, and validate errors when they do not](scenarios-live-in-files-named-for-them.md) — pikkuScenario/pikkuFeature/pikkuScenarioStep must be in *.scenario.ts, *.scenarios.ts or *.steps.ts, and definePersonas/runVirtualUser in *.virtual-user.ts or *.vu.ts — an error, because the mixing is only cheap to undo while it is one file
|
|
74
98
|
- [Scope roots may be co-declared by an addon and its host app](scope-roots-may-be-co-declared-by-an-addon-and-its-host-app.md) — flattenScopeDefinitions dedupes ids because the same root can legitimately be declared twice, and every consumer requires one entry per scope
|
|
75
99
|
- [Serverless channel disconnect must tolerate a missing channel](serverless-channel-disconnect-must-tolerate-a-missing-channel.md) — A failed channel lookup on disconnect returns quietly, because serverless runtimes deliver disconnect more than once
|
|
100
|
+
- [sideEffects is an allowlist, because core genuinely has some](side-effects-are-an-allowlist-not-a-boolean.md) — The error registry is built by addError calls that run on import, so claiming sideEffects:false would let a bundler drop it and leave getErrorResponse unable to find any error
|
|
101
|
+
- [Speech synthesis picks a voice per sentence but announces a limitation once](speech-synthesis-picks-a-voice-per-sentence-and-warns-once.md) — A bilingual reply should speak the half it can, and repeating the notice for every sentence would bury the reply itself
|
|
102
|
+
- [The actor's instructions say the word "json" because a degraded gateway demands it](the-actor-prompt-says-json-because-of-json-object-mode.md) — A gateway that cannot take a JSON schema falls back to OpenAI's json_object mode, which refuses any request whose prompt does not contain the word
|
|
103
|
+
- [The agent `done` event goes through the middleware and is awaited](the-agent-done-event-goes-through-the-middleware-and-is-awaited.md) — `done` is the only end-of-reply signal a stream hook gets, and buffering hooks flush on it — sending it raw discards work already paid for
|
|
104
|
+
- [The API report pins members, because the export list only pins names](the-api-report-pins-members-not-just-names.md) — public-surface.json catches an export appearing or vanishing; it cannot see a method added to an interface, which is the change that breaks a consumer's build
|
|
76
105
|
- [The dev queue copies prod timing and serialization semantics](the-dev-queue-copies-prod-timing-and-serialization-semantics.md) — InMemoryQueueService dispatches via setTimeout, retries with backoff, and JSON round-trips every payload so dev behaviour matches a real backend
|
|
106
|
+
- [The ecosystem entry point carries the adapter surface, so the root can promise stability](the-ecosystem-entry-point-carries-the-adapter-surface.md) — runPikkuFunc and the singleton-service accessors are what a runtime adapter reaches for; their signatures move, and the package root should not promise otherwise
|
|
77
107
|
- [The embedding model is pinned per service and doc/query embedding is split](the-embedding-model-is-pinned-per-service-and-doc-query-embedding-is-split.md) — AIEmbeddingService fixes its model at construction so index and query share a vector space, and separates embedDocuments from embedQuery for asymmetric models
|
|
78
108
|
- [The in-memory workflow service is inline-only and single-process](the-in-memory-workflow-service-is-inline-only-and-single-process.md) — InMemoryWorkflowService wires no queues and implements withRunLock/withStepLock as pass-throughs, because inline execution has no second holder to exclude
|
|
79
109
|
- [The KEK salt is scoped to the key version, not the secret](the-kek-salt-is-scoped-to-the-key-version.md) — One stored salt per key version means N secrets cost one derivation, which is the point of envelope encryption
|
|
110
|
+
- [The middleware resolution cache is deliberately unbounded](the-middleware-resolution-cache-is-deliberately-unbounded.md) — Its keyspace is the set of registered wires, not request traffic, and middleware is dynamic — so eviction would buy nothing and cost the dedupe guarantee
|
|
111
|
+
- [The per-invocation rpc view is a class, because an object literal with a getter is slow to build](the-per-invocation-rpc-view-is-a-class.md) — An accessor declared on an object literal is defined per instance, which drops the literal off V8's fast construction path — measured at 1.15µs against 0.47µs, on every request
|
|
112
|
+
- [The persona runtime is exported from @pikku/core/persona, never from services](the-persona-runtime-is-exported-from-the-persona-entry-point.md) — Those values reach the actor-flow and agent runners, which no production server runs — and an unbundled deploy loads whatever the import graph names
|
|
80
113
|
- [The schema service is never stubbed, or tests validate nothing](the-schema-service-is-never-stubbed.md) — createStubProxy returns undefined for the schema property so the real schema service is built — a stubbed one turns validation into a silent no-op
|
|
81
|
-
- [
|
|
82
|
-
- [
|
|
114
|
+
- [A voice turn's transcript is sent before the run starts, on the raw channel](the-transcript-event-is-sent-ahead-of-the-run.md) — The client does not know what it said, and an answer starts streaming within a few hundred milliseconds — a question arriving after its answer reads as the wrong question
|
|
115
|
+
- [The catalogue is the only gate on what a virtual user may call](the-virtual-user-catalogue-is-the-only-gate-on-what-may-be-called.md) — Narrowing happens once at derivation, so there is no second check in the run loop that could drift out of step with it
|
|
116
|
+
- [The worker disposition is the only one not testing anything](the-worker-disposition-is-the-one-that-is-not-testing.md) — It is doing the job, so it abandons rarely, runs cool, and is told to stop and say so rather than guess
|
|
117
|
+
- [Thread history records what the model was asked, which for a spoken turn is the transcript](thread-history-records-the-transcript-not-the-audio.md) — The wire carried a base64 audio blob; persisting it writes megabytes of unreadable data and loses the only readable record of the turn
|
|
83
118
|
- [Trigger declaration is split from trigger source](trigger-declaration-is-split-from-trigger-source.md) — Triggers are declared everywhere but subscribed only in the trigger worker, so app processes never open the underlying subscription
|
|
84
119
|
- [TypedSecretService caches for the process lifetime](typed-secret-service-caches-for-the-process-lifetime.md) — Resolved secrets are cached with no TTL, so a secret rotated out of band is not picked up until restart — tracked as pikkujs/pikku#964
|
|
85
|
-
- [Both validators check personas through one shared module, and a persona counts from either the meta or the source](validate-checks-personas-through-a-shared-module.md) — workspace validate and fabric validate are separate implementations, so the persona/actor/knowledge checks live in persona-checks.ts and are called by both
|
|
120
|
+
- [Both validators check personas through one shared module, and a persona counts from either the meta or the source](validate-checks-personas-through-a-shared-module.md) — workspace validate and fabric validate are separate implementations, so the persona/actor/knowledge checks live in persona-checks.ts and are called by both — and a persona is counted from the generated meta or a definePersonas() source scan, whichever answers first
|
|
121
|
+
- [pikku validate runs every applicable check, it does not detect a project kind](validate-runs-checks-by-precondition.md) — one `pikku validate` replaces `pikku workspace validate`; checks declare a precondition and run wherever it holds, because the repos that matter are an app and a pile of addons at the same time
|
|
122
|
+
- [A scenario's step order for a virtual user is insertion order, not a graph traversal](virtual-user-step-order-comes-from-insertion-order.md) — The CLI writes nodes in declaration order, so following `next` would buy an ordering that is already true and cost a traversal that has to interpret branches
|
|
123
|
+
- [Only an explicit `false` silences a spoken reply](voice-output-speaks-unless-voice-input-explicitly-says-otherwise.md) — The key being absent means no voice input is wired and nothing reported either way, so those callers keep the behaviour they had before the option existed
|
|
86
124
|
- [Webhook delivery history records every attempt, best effort](webhook-delivery-history-records-every-attempt-best-effort.md) — The webhook worker persists each attempt before it throws, and a failure to persist is logged rather than allowed to mask the delivery result
|
|
87
125
|
- [Webhook service collaborators are constructor args, not locator lookups](webhook-service-collaborators-are-constructor-args-not-locator-lookups.md) — QueueWebhookService takes its queue as a constructor parameter so a project wiring webhooks without a queue fails to compile instead of at first send
|
|
88
126
|
- [Whether a run is inline is read from the run record](whether-a-run-is-inline-is-read-from-the-run-record.md) — The runContexts map is a read-through cache over WorkflowRun.inline and a lifetime for replay ordinals, never the answer to what a run is
|
|
127
|
+
- [Wiring registries erase the generics their wire* functions capture](wiring-registries-erase-the-generics-their-wire-functions-capture.md) — A registry is typed at unknown so every wiring shares one map; storing a generic config in it needs an assertion, because function parameters are contravariant
|
|
89
128
|
- [Workflow approval expiry is decided from a recorded deadline, not from a timer firing](workflow-approval-expiry-is-decided-from-a-recorded-deadline.md) — The wake-up job is best-effort liveness; losing, duplicating or delaying it cannot change the gate's answer
|
|
90
129
|
- [Core declares the scenario browser surface structurally and never imports a driver](workflow-core-never-imports-a-browser-driver.md) — `@pikku/core` must stay dependency-free for edge runtimes, so playwright augments the interface instead of being imported by it
|
|
91
130
|
- [Workflow DSL meta keeps runtime expressions in their own field, apart from literal values](workflow-dsl-meta-separates-runtime-expressions-from-literals.md) — A string `value` regenerates as a string literal; an `expression` regenerates as code, so the two can never share a field
|
package/knowledge/decisions/internals/only-exposed-functions-enter-a-virtual-user-catalogue.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: Only functions marked `expose: true` enter a virtual user's catalogue
|
|
4
|
+
description: Absent is not permissive — an unexposed function 404s over rpc, so offering one spends a step to learn nothing about the product
|
|
5
|
+
tags: core, virtual-user
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Only functions marked `expose: true` enter a virtual user's catalogue
|
|
9
|
+
|
|
10
|
+
`expose: true` is what puts a function on the rpc transport, which is what the
|
|
11
|
+
shipped target is called over. The catalogue therefore treats an absent `expose`
|
|
12
|
+
as excluded rather than as unspecified-and-allowed.
|
|
13
|
+
|
|
14
|
+
Absent is not permissive here: an unexposed function returns 404, and offering
|
|
15
|
+
it costs the user a step and teaches nothing about the product. On the e2e app,
|
|
16
|
+
34 of 72 functions are in exactly that state — close to half a catalogue that
|
|
17
|
+
cannot be called at all.
|
|
18
|
+
|
|
19
|
+
**What this rules out:** defaulting the check to "include unless explicitly
|
|
20
|
+
hidden", which reads as the safer default and produces a catalogue that is
|
|
21
|
+
mostly dead ends.
|