@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
|
@@ -12,7 +12,11 @@ import type {
|
|
|
12
12
|
} from './ai-agent.types.js'
|
|
13
13
|
import type { AIAgentRunnerParams } from '../../services/ai-agent-runner-service.js'
|
|
14
14
|
import { PikkuError } from '../../errors/error-handler.js'
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
checkAuthPermissions,
|
|
17
|
+
runPermissions,
|
|
18
|
+
type PermissionWire,
|
|
19
|
+
} from '../../permissions.js'
|
|
16
20
|
import { AIProviderNotConfiguredError } from '../../errors/errors.js'
|
|
17
21
|
import { ForbiddenError } from '../../errors/errors.js'
|
|
18
22
|
import { verifyScopes } from '../../scopes.js'
|
|
@@ -319,8 +323,8 @@ export async function assertAgentAuthorized(
|
|
|
319
323
|
|
|
320
324
|
await runPermissions({
|
|
321
325
|
funcPermissions: agent.permissions,
|
|
322
|
-
services: singletonServices
|
|
323
|
-
wire: wire as
|
|
326
|
+
services: singletonServices,
|
|
327
|
+
wire: wire as PermissionWire,
|
|
324
328
|
data: {},
|
|
325
329
|
packageName,
|
|
326
330
|
label: 'agent',
|
|
@@ -395,7 +399,7 @@ export function createScopedChannel(
|
|
|
395
399
|
toolName: event.toolName,
|
|
396
400
|
args: event.args,
|
|
397
401
|
...(event.reason !== undefined ? { reason: event.reason } : {}),
|
|
398
|
-
runId:
|
|
402
|
+
runId: event.runId,
|
|
399
403
|
})
|
|
400
404
|
return
|
|
401
405
|
}
|
|
@@ -940,10 +944,7 @@ export async function prepareAgentRun(
|
|
|
940
944
|
|
|
941
945
|
let messages: AIMessage[] = []
|
|
942
946
|
if (storage) {
|
|
943
|
-
//
|
|
944
|
-
// thread. In voice the next turn lands within a second or two, so without
|
|
945
|
-
// this the model would load context that is missing the very thing it is
|
|
946
|
-
// about to be asked about.
|
|
947
|
+
// knowledge: decisions/internals/agent-context-waits-for-a-tool-result-still-being-written.md
|
|
947
948
|
await awaitPendingInterruptNote(threadId)
|
|
948
949
|
messages = await storage.getMessages(threadId, {
|
|
949
950
|
lastN: memoryConfig?.lastMessages ?? 20,
|
|
@@ -959,10 +960,7 @@ export async function prepareAgentRun(
|
|
|
959
960
|
|
|
960
961
|
const userContent: AIMessage['content'] = input.attachments?.length
|
|
961
962
|
? [
|
|
962
|
-
//
|
|
963
|
-
// real turn — a spoken one carries audio and no text at all — and an
|
|
964
|
-
// empty text part alongside it is a part providers are entitled to
|
|
965
|
-
// reject, for a caller who never wrote one.
|
|
963
|
+
// knowledge: decisions/internals/an-empty-text-part-is-omitted-from-an-agent-message.md
|
|
966
964
|
...(input.message
|
|
967
965
|
? [{ type: 'text' as const, text: input.message }]
|
|
968
966
|
: []),
|
|
@@ -707,7 +707,7 @@ describe('resumeAIAgentSync', () => {
|
|
|
707
707
|
createdAt: new Date(),
|
|
708
708
|
updatedAt: new Date(),
|
|
709
709
|
}),
|
|
710
|
-
resolveApproval: async () =>
|
|
710
|
+
resolveApproval: async () => true,
|
|
711
711
|
updateRun: async () => {},
|
|
712
712
|
},
|
|
713
713
|
} as any)
|
|
@@ -784,6 +784,7 @@ describe('resumeAIAgentSync', () => {
|
|
|
784
784
|
},
|
|
785
785
|
resolveApproval: async (toolCallId: string, status: string) => {
|
|
786
786
|
resolveCalls.push({ toolCallId, status })
|
|
787
|
+
return true
|
|
787
788
|
},
|
|
788
789
|
},
|
|
789
790
|
aiStorage: {
|
|
@@ -816,6 +817,84 @@ describe('resumeAIAgentSync', () => {
|
|
|
816
817
|
])
|
|
817
818
|
})
|
|
818
819
|
|
|
820
|
+
test('an approver that loses the claim never runs the tool', async () => {
|
|
821
|
+
addTestAgent('claim-agent')
|
|
822
|
+
pikkuState(null, 'agent', 'agentsMeta')['claim-agent'].tools = ['refund']
|
|
823
|
+
pikkuState(null, 'rpc', 'meta').refund = 'refund'
|
|
824
|
+
pikkuState(null, 'function', 'meta').refund = {
|
|
825
|
+
description: 'Refund',
|
|
826
|
+
sessionless: true,
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
let executions = 0
|
|
830
|
+
pikkuState(null, 'function', 'functions').set('refund', {
|
|
831
|
+
func: async () => {
|
|
832
|
+
executions++
|
|
833
|
+
return { refunded: true }
|
|
834
|
+
},
|
|
835
|
+
})
|
|
836
|
+
|
|
837
|
+
const run: AgentRunState = {
|
|
838
|
+
runId: 'run-claim',
|
|
839
|
+
agentName: 'claim-agent',
|
|
840
|
+
threadId: 'thread-claim',
|
|
841
|
+
resourceId: 'resource-claim',
|
|
842
|
+
status: 'suspended',
|
|
843
|
+
suspendReason: 'approval',
|
|
844
|
+
pendingApprovals: [
|
|
845
|
+
{
|
|
846
|
+
type: 'tool-call',
|
|
847
|
+
toolCallId: 'tc-claim',
|
|
848
|
+
toolName: 'refund',
|
|
849
|
+
args: {},
|
|
850
|
+
},
|
|
851
|
+
],
|
|
852
|
+
usage: { inputTokens: 0, outputTokens: 0, model: 'test/test-model' },
|
|
853
|
+
createdAt: new Date(),
|
|
854
|
+
updatedAt: new Date(),
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// Every concurrent approver reads the same `suspended` run with the same
|
|
858
|
+
// pending list; the store hands the claim to exactly one of them.
|
|
859
|
+
let claimsLeft = 1
|
|
860
|
+
pikkuState(null, 'package', 'singletonServices', {
|
|
861
|
+
logger: {
|
|
862
|
+
info: () => {},
|
|
863
|
+
warn: () => {},
|
|
864
|
+
error: () => {},
|
|
865
|
+
debug: () => {},
|
|
866
|
+
},
|
|
867
|
+
aiAgentRunner: {
|
|
868
|
+
run: async (): Promise<AIAgentStepResult> =>
|
|
869
|
+
makeStepResult({
|
|
870
|
+
text: 'Refunded',
|
|
871
|
+
usage: { inputTokens: 1, outputTokens: 1 },
|
|
872
|
+
}),
|
|
873
|
+
},
|
|
874
|
+
aiRunState: {
|
|
875
|
+
getRun: async () => run,
|
|
876
|
+
createRun: async () => 'unused',
|
|
877
|
+
updateRun: async () => {},
|
|
878
|
+
resolveApproval: async () => claimsLeft-- > 0,
|
|
879
|
+
},
|
|
880
|
+
aiStorage: {
|
|
881
|
+
saveMessages: async () => {},
|
|
882
|
+
getMessages: async () => [],
|
|
883
|
+
},
|
|
884
|
+
} as any)
|
|
885
|
+
|
|
886
|
+
const approvals = [{ toolCallId: 'tc-claim', approved: true }]
|
|
887
|
+
await resumeAIAgentSync('run-claim', approvals, asOwner('resource-claim'))
|
|
888
|
+
assert.equal(executions, 1, 'the winner ran the tool once')
|
|
889
|
+
|
|
890
|
+
await assert.rejects(
|
|
891
|
+
() =>
|
|
892
|
+
resumeAIAgentSync('run-claim', approvals, asOwner('resource-claim')),
|
|
893
|
+
/already resolved by another caller/
|
|
894
|
+
)
|
|
895
|
+
assert.equal(executions, 1, 'the loser did not run it a second time')
|
|
896
|
+
})
|
|
897
|
+
|
|
819
898
|
test('records denied approvals and rejects agent mismatches', async () => {
|
|
820
899
|
addTestAgent('resume-agent')
|
|
821
900
|
|
|
@@ -861,6 +940,7 @@ describe('resumeAIAgentSync', () => {
|
|
|
861
940
|
updateRun: async () => {},
|
|
862
941
|
resolveApproval: async (toolCallId: string, status: string) => {
|
|
863
942
|
resolveCalls.push({ toolCallId, status })
|
|
943
|
+
return true
|
|
864
944
|
},
|
|
865
945
|
},
|
|
866
946
|
aiStorage: {
|
|
@@ -959,7 +1039,7 @@ describe('resumeAIAgentSync', () => {
|
|
|
959
1039
|
},
|
|
960
1040
|
aiRunState: {
|
|
961
1041
|
getRun: async () => run,
|
|
962
|
-
resolveApproval: async () =>
|
|
1042
|
+
resolveApproval: async () => true,
|
|
963
1043
|
updateRun: async () => {},
|
|
964
1044
|
},
|
|
965
1045
|
aiStorage: {
|
|
@@ -43,6 +43,12 @@ import {
|
|
|
43
43
|
trackToolExecution,
|
|
44
44
|
} from './ai-agent-interrupt.js'
|
|
45
45
|
import { pikkuState, getSingletonServices } from '../../pikku-state.js'
|
|
46
|
+
import {
|
|
47
|
+
applyInputMiddleware,
|
|
48
|
+
describeApprovals,
|
|
49
|
+
notifyAfterStep,
|
|
50
|
+
toAccumulatedStep,
|
|
51
|
+
} from './ai-agent-turn.js'
|
|
46
52
|
import { resolveModelConfig } from './ai-agent-model-config.js'
|
|
47
53
|
import { AIProviderNotConfiguredError } from '../../errors/errors.js'
|
|
48
54
|
import { randomUUID } from './ai-agent-utils.js'
|
|
@@ -147,26 +153,20 @@ export async function runAIAgent(
|
|
|
147
153
|
// One bag per run, shared by every middleware — see PikkuAIMiddlewareHooks.
|
|
148
154
|
const sharedNotes: Record<string, unknown> = {}
|
|
149
155
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
messages:
|
|
156
|
-
instructions:
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
modifiedInstructions = result.instructions
|
|
161
|
-
}
|
|
162
|
-
}
|
|
156
|
+
const { messages: modifiedMessages, instructions: modifiedInstructions } =
|
|
157
|
+
await applyInputMiddleware(
|
|
158
|
+
aiMiddlewares,
|
|
159
|
+
singletonServices,
|
|
160
|
+
{
|
|
161
|
+
messages: runnerParams.messages,
|
|
162
|
+
instructions: runnerParams.instructions,
|
|
163
|
+
},
|
|
164
|
+
sharedNotes
|
|
165
|
+
)
|
|
163
166
|
runnerParams.messages = modifiedMessages
|
|
164
167
|
runnerParams.instructions = modifiedInstructions
|
|
165
168
|
|
|
166
|
-
//
|
|
167
|
-
// transcript rather than the base64 audio that arrived — see the same note on
|
|
168
|
-
// the streaming path. Identity-checked, because a middleware may legitimately
|
|
169
|
-
// replace the message list with something unrelated to this turn.
|
|
169
|
+
// knowledge: decisions/internals/thread-history-records-the-transcript-not-the-audio.md
|
|
170
170
|
const lastModified = modifiedMessages[modifiedMessages.length - 1]
|
|
171
171
|
const persistedUserMessage =
|
|
172
172
|
lastModified?.id === userMessage.id ? lastModified : userMessage
|
|
@@ -181,10 +181,7 @@ export async function runAIAgent(
|
|
|
181
181
|
updatedAt: new Date(),
|
|
182
182
|
})
|
|
183
183
|
|
|
184
|
-
//
|
|
185
|
-
// visible to `interruptAIAgent` through `aiRunState` either way, so skipping
|
|
186
|
-
// this would leave that call finding the run, passing the ownership check and
|
|
187
|
-
// then failing to stop it — reported as if it were running on another host.
|
|
184
|
+
// knowledge: decisions/internals/a-non-streaming-agent-run-registers-with-airunstate-too.md
|
|
188
185
|
const interruptHandle = registerInterruptibleRun(runId)
|
|
189
186
|
runnerParams.abortSignal = interruptHandle.signal
|
|
190
187
|
runnerParams.tools = trackToolExecution(runnerParams.tools, interruptHandle)
|
|
@@ -216,52 +213,15 @@ export async function runAIAgent(
|
|
|
216
213
|
totalUsage.inputTokens += stepResult.usage.inputTokens
|
|
217
214
|
totalUsage.outputTokens += stepResult.usage.outputTokens
|
|
218
215
|
|
|
219
|
-
|
|
220
|
-
if (mw.afterStep) {
|
|
221
|
-
await mw.afterStep(singletonServices, {
|
|
222
|
-
stepNumber: step,
|
|
223
|
-
text: stepResult.text,
|
|
224
|
-
toolCalls: stepResult.toolCalls,
|
|
225
|
-
toolResults: stepResult.toolResults,
|
|
226
|
-
usage: stepResult.usage,
|
|
227
|
-
finishReason: stepResult.finishReason,
|
|
228
|
-
})
|
|
229
|
-
}
|
|
230
|
-
}
|
|
216
|
+
await notifyAfterStep(aiMiddlewares, singletonServices, step, stepResult)
|
|
231
217
|
|
|
232
|
-
accumulatedSteps.push(
|
|
233
|
-
usage: stepResult.usage,
|
|
234
|
-
toolCalls: stepResult.toolCalls.map((tc) => {
|
|
235
|
-
const tr = stepResult.toolResults.find(
|
|
236
|
-
(r) => r.toolCallId === tc.toolCallId
|
|
237
|
-
)
|
|
238
|
-
return {
|
|
239
|
-
name: tc.toolName,
|
|
240
|
-
args: tc.args as Record<string, unknown>,
|
|
241
|
-
result:
|
|
242
|
-
typeof tr?.result === 'string'
|
|
243
|
-
? tr.result
|
|
244
|
-
: JSON.stringify(tr?.result ?? ''),
|
|
245
|
-
}
|
|
246
|
-
}),
|
|
247
|
-
})
|
|
218
|
+
accumulatedSteps.push(toAccumulatedStep(stepResult))
|
|
248
219
|
|
|
249
220
|
if (stepResult.toolCalls.length === 0) break
|
|
250
221
|
|
|
251
222
|
const approvalsNeeded = checkForApprovals(stepResult, runnerParams.tools)
|
|
252
223
|
if (approvalsNeeded.length > 0) {
|
|
253
|
-
|
|
254
|
-
const toolDef = runnerParams.tools.find(
|
|
255
|
-
(t) => t.name === approval.toolName
|
|
256
|
-
)
|
|
257
|
-
if (toolDef?.approvalDescriptionFn && !approval.reason) {
|
|
258
|
-
try {
|
|
259
|
-
approval.reason = await toolDef.approvalDescriptionFn(
|
|
260
|
-
approval.args
|
|
261
|
-
)
|
|
262
|
-
} catch {}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
224
|
+
await describeApprovals(approvalsNeeded, runnerParams.tools)
|
|
265
225
|
|
|
266
226
|
const pendingApprovals = approvalsNeeded.map((a) =>
|
|
267
227
|
a.agentRunId
|
|
@@ -385,10 +345,7 @@ export async function runAIAgent(
|
|
|
385
345
|
usage: totalUsage,
|
|
386
346
|
}
|
|
387
347
|
} catch (error) {
|
|
388
|
-
//
|
|
389
|
-
// becomes an `errorMessage`. Unlike the streaming path there is no partial
|
|
390
|
-
// reply to hand back — nothing was delivered — so the caller gets a typed
|
|
391
|
-
// throw it can tell apart from a provider outage instead of a result.
|
|
348
|
+
// knowledge: decisions/internals/an-agent-interrupt-is-not-a-failure.md
|
|
392
349
|
const interruption = interruptHandle.interruption
|
|
393
350
|
if (interruption || isAbortError(error)) {
|
|
394
351
|
await aiRunState.updateRun(runId, { status: 'interrupted' })
|
|
@@ -470,11 +427,24 @@ export async function resumeAIAgentSync(
|
|
|
470
427
|
|
|
471
428
|
const savedPendingApprovals = [...(run.pendingApprovals ?? [])]
|
|
472
429
|
|
|
430
|
+
// The read above is not a claim — concurrent resumes all see the same pending
|
|
431
|
+
// list. `resolveApproval` is the claim, and only the caller it returns true
|
|
432
|
+
// for may run the tool.
|
|
433
|
+
const claimedIds = new Set<string>()
|
|
473
434
|
for (const { toolCallId, approved } of approvals) {
|
|
474
|
-
await aiRunState.resolveApproval(
|
|
435
|
+
const claimed = await aiRunState.resolveApproval(
|
|
475
436
|
toolCallId,
|
|
476
437
|
approved ? 'approved' : 'denied'
|
|
477
438
|
)
|
|
439
|
+
if (claimed) {
|
|
440
|
+
claimedIds.add(toolCallId)
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (approvals.length > 0 && claimedIds.size === 0) {
|
|
445
|
+
throw new Error(
|
|
446
|
+
`Approvals for run ${runId} were already resolved by another caller`
|
|
447
|
+
)
|
|
478
448
|
}
|
|
479
449
|
|
|
480
450
|
const { tools } = await buildToolDefs(
|
|
@@ -498,6 +468,8 @@ export async function resumeAIAgentSync(
|
|
|
498
468
|
if (pending.type !== 'tool-call') continue
|
|
499
469
|
|
|
500
470
|
const toolCallId = pending.toolCallId
|
|
471
|
+
if (!claimedIds.has(toolCallId)) continue
|
|
472
|
+
|
|
501
473
|
let resultStr: string
|
|
502
474
|
|
|
503
475
|
if (rejectedIds.has(toolCallId)) {
|
|
@@ -623,19 +595,13 @@ async function continueAfterToolResultSync(
|
|
|
623
595
|
// One bag per run, shared by every middleware — see PikkuAIMiddlewareHooks.
|
|
624
596
|
const sharedNotes: Record<string, unknown> = {}
|
|
625
597
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
shared: sharedNotes,
|
|
634
|
-
})
|
|
635
|
-
modifiedMessages = result.messages
|
|
636
|
-
modifiedInstructions = result.instructions
|
|
637
|
-
}
|
|
638
|
-
}
|
|
598
|
+
const { messages: modifiedMessages, instructions: modifiedInstructions } =
|
|
599
|
+
await applyInputMiddleware(
|
|
600
|
+
aiMiddlewares,
|
|
601
|
+
singletonServices,
|
|
602
|
+
{ messages: trimmedMessages, instructions: instructions },
|
|
603
|
+
sharedNotes
|
|
604
|
+
)
|
|
639
605
|
|
|
640
606
|
const { tools: resumeTools } = await buildToolDefs(
|
|
641
607
|
params,
|
|
@@ -684,52 +650,15 @@ async function continueAfterToolResultSync(
|
|
|
684
650
|
totalUsage.inputTokens += stepResult.usage.inputTokens
|
|
685
651
|
totalUsage.outputTokens += stepResult.usage.outputTokens
|
|
686
652
|
|
|
687
|
-
|
|
688
|
-
if (mw.afterStep) {
|
|
689
|
-
await mw.afterStep(singletonServices, {
|
|
690
|
-
stepNumber: step,
|
|
691
|
-
text: stepResult.text,
|
|
692
|
-
toolCalls: stepResult.toolCalls,
|
|
693
|
-
toolResults: stepResult.toolResults,
|
|
694
|
-
usage: stepResult.usage,
|
|
695
|
-
finishReason: stepResult.finishReason,
|
|
696
|
-
})
|
|
697
|
-
}
|
|
698
|
-
}
|
|
653
|
+
await notifyAfterStep(aiMiddlewares, singletonServices, step, stepResult)
|
|
699
654
|
|
|
700
|
-
accumulatedSteps.push(
|
|
701
|
-
usage: stepResult.usage,
|
|
702
|
-
toolCalls: stepResult.toolCalls.map((tc) => {
|
|
703
|
-
const tr = stepResult.toolResults.find(
|
|
704
|
-
(r) => r.toolCallId === tc.toolCallId
|
|
705
|
-
)
|
|
706
|
-
return {
|
|
707
|
-
name: tc.toolName,
|
|
708
|
-
args: tc.args as Record<string, unknown>,
|
|
709
|
-
result:
|
|
710
|
-
typeof tr?.result === 'string'
|
|
711
|
-
? tr.result
|
|
712
|
-
: JSON.stringify(tr?.result ?? ''),
|
|
713
|
-
}
|
|
714
|
-
}),
|
|
715
|
-
})
|
|
655
|
+
accumulatedSteps.push(toAccumulatedStep(stepResult))
|
|
716
656
|
|
|
717
657
|
if (stepResult.toolCalls.length === 0) break
|
|
718
658
|
|
|
719
659
|
const approvalsNeeded = checkForApprovals(stepResult, runnerParams.tools)
|
|
720
660
|
if (approvalsNeeded.length > 0) {
|
|
721
|
-
|
|
722
|
-
const toolDef = runnerParams.tools.find(
|
|
723
|
-
(t) => t.name === approval.toolName
|
|
724
|
-
)
|
|
725
|
-
if (toolDef?.approvalDescriptionFn && !approval.reason) {
|
|
726
|
-
try {
|
|
727
|
-
approval.reason = await toolDef.approvalDescriptionFn(
|
|
728
|
-
approval.args
|
|
729
|
-
)
|
|
730
|
-
} catch {}
|
|
731
|
-
}
|
|
732
|
-
}
|
|
661
|
+
await describeApprovals(approvalsNeeded, runnerParams.tools)
|
|
733
662
|
|
|
734
663
|
const pendingApprovals = approvalsNeeded.map((a) =>
|
|
735
664
|
a.agentRunId
|
|
@@ -832,7 +761,7 @@ async function continueAfterToolResultSync(
|
|
|
832
761
|
run.threadId,
|
|
833
762
|
run.resourceId,
|
|
834
763
|
memoryConfig,
|
|
835
|
-
null
|
|
764
|
+
null,
|
|
836
765
|
{
|
|
837
766
|
...result,
|
|
838
767
|
text: outputText,
|
|
@@ -1629,6 +1629,7 @@ describe('resumeAIAgent', () => {
|
|
|
1629
1629
|
getRun: async () => remainingRun,
|
|
1630
1630
|
resolveApproval: async (toolCallId: string, status: string) => {
|
|
1631
1631
|
resolveCalls.push({ toolCallId, status })
|
|
1632
|
+
return true
|
|
1632
1633
|
},
|
|
1633
1634
|
updateRun: async (_runId: string, patch: any) => {
|
|
1634
1635
|
updates.push(patch)
|
|
@@ -1750,6 +1751,7 @@ describe('resumeAIAgent', () => {
|
|
|
1750
1751
|
},
|
|
1751
1752
|
resolveApproval: async (toolCallId: string, status: string) => {
|
|
1752
1753
|
resolveCalls.push({ toolCallId, status })
|
|
1754
|
+
return true
|
|
1753
1755
|
},
|
|
1754
1756
|
updateRun: async (_runId: string, patch: any) => {
|
|
1755
1757
|
updates.push(patch)
|
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
AIAgentMemoryConfig,
|
|
11
11
|
} from './ai-agent.types.js'
|
|
12
12
|
import { pikkuState, getSingletonServices } from '../../pikku-state.js'
|
|
13
|
+
import { applyInputMiddleware } from './ai-agent-turn.js'
|
|
13
14
|
import { AIProviderNotConfiguredError } from '../../errors/errors.js'
|
|
14
15
|
import { randomUUID } from './ai-agent-utils.js'
|
|
15
16
|
import { SPOKEN_TRANSCRIPT } from './voice-input.js'
|
|
@@ -676,38 +677,26 @@ export async function streamAIAgent(
|
|
|
676
677
|
// One bag per run, shared by every middleware — see PikkuAIMiddlewareHooks.
|
|
677
678
|
const sharedNotes: Record<string, unknown> = {}
|
|
678
679
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
messages:
|
|
685
|
-
instructions:
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
modifiedInstructions = result.instructions
|
|
690
|
-
}
|
|
691
|
-
}
|
|
680
|
+
const { messages: modifiedMessages, instructions: modifiedInstructions } =
|
|
681
|
+
await applyInputMiddleware(
|
|
682
|
+
aiMiddlewares,
|
|
683
|
+
singletonServices,
|
|
684
|
+
{
|
|
685
|
+
messages: runnerParams.messages,
|
|
686
|
+
instructions: runnerParams.instructions,
|
|
687
|
+
},
|
|
688
|
+
sharedNotes
|
|
689
|
+
)
|
|
692
690
|
runnerParams.messages = modifiedMessages
|
|
693
691
|
runnerParams.instructions = modifiedInstructions
|
|
694
692
|
|
|
695
|
-
//
|
|
696
|
-
// has no idea what it said; until this arrives its own message is a blank
|
|
697
|
-
// bubble. Ahead of the run rather than alongside it because the answer starts
|
|
698
|
-
// streaming within a few hundred milliseconds, and a question that appears
|
|
699
|
-
// after its answer reads as the wrong question.
|
|
693
|
+
// knowledge: decisions/internals/the-transcript-event-is-sent-ahead-of-the-run.md
|
|
700
694
|
const transcript = sharedNotes[SPOKEN_TRANSCRIPT]
|
|
701
695
|
if (typeof transcript === 'string') {
|
|
702
696
|
channel.send({ type: 'transcript', text: transcript })
|
|
703
697
|
}
|
|
704
698
|
|
|
705
|
-
//
|
|
706
|
-
// spoken turn that is not what arrived over the wire: the wire carried a
|
|
707
|
-
// base64 audio blob, and persisting it would write megabytes of unreadable
|
|
708
|
-
// data into the history while losing the only readable record of the turn.
|
|
709
|
-
// Identity-checked rather than assumed — a middleware is free to rewrite the
|
|
710
|
-
// message list into something with no relation to the turn.
|
|
699
|
+
// knowledge: decisions/internals/thread-history-records-the-transcript-not-the-audio.md
|
|
711
700
|
const lastModified = modifiedMessages[modifiedMessages.length - 1]
|
|
712
701
|
const persistedUserMessage =
|
|
713
702
|
lastModified?.id === userMessage.id ? lastModified : userMessage
|
|
@@ -768,7 +757,7 @@ export async function streamAIAgent(
|
|
|
768
757
|
{
|
|
769
758
|
wireInheritedChannelMiddleware: meta?.channelMiddleware,
|
|
770
759
|
wireChannelMiddleware: [
|
|
771
|
-
...(
|
|
760
|
+
...(agent.channelMiddleware ?? []),
|
|
772
761
|
...streamMiddleware,
|
|
773
762
|
],
|
|
774
763
|
}
|
|
@@ -792,10 +781,7 @@ export async function streamAIAgent(
|
|
|
792
781
|
|
|
793
782
|
const credentialFilteredChannel: AIStreamChannel = {
|
|
794
783
|
...wrappedChannel,
|
|
795
|
-
//
|
|
796
|
-
// swallowing the promise here makes every `await channel.send(...)` upstream
|
|
797
|
-
// a no-op — including the one that waits for a buffering hook to flush
|
|
798
|
-
// before the channel closes.
|
|
784
|
+
// knowledge: decisions/internals/an-agent-stream-send-must-return-the-inner-sends-promise.md
|
|
799
785
|
send: (event: AIStreamEvent) => {
|
|
800
786
|
if (
|
|
801
787
|
event.type === 'tool-result' &&
|
|
@@ -872,11 +858,7 @@ export async function streamAIAgent(
|
|
|
872
858
|
runId
|
|
873
859
|
)
|
|
874
860
|
|
|
875
|
-
//
|
|
876
|
-
// `done` is the only signal a stream hook gets that the reply is over, and
|
|
877
|
-
// the ones that buffer need it: `voiceOutput` speaks a trailing fragment
|
|
878
|
-
// that never reached a full stop, and waits for audio it has already paid
|
|
879
|
-
// to synthesize. Sent raw, that work is discarded by the `close()` below.
|
|
861
|
+
// knowledge: decisions/internals/the-agent-done-event-goes-through-the-middleware-and-is-awaited.md
|
|
880
862
|
await outputChannel.send({ type: 'done' })
|
|
881
863
|
channel.close()
|
|
882
864
|
return persistingChannel.fullText
|
|
@@ -979,12 +961,7 @@ export async function interruptAIAgent(
|
|
|
979
961
|
reason: input.reason ?? 'user',
|
|
980
962
|
})
|
|
981
963
|
|
|
982
|
-
//
|
|
983
|
-
// another instance. Returning a bare `false` there is indistinguishable from
|
|
984
|
-
// "it already finished", so the one deployment shape this registry does not
|
|
985
|
-
// cover fails silently — an agent that simply refuses to shut up, with nothing
|
|
986
|
-
// in the logs. Say so instead. See `signalRunInterrupt` for the fix (fan the
|
|
987
|
-
// interrupt out over eventHub so every instance tries locally).
|
|
964
|
+
// knowledge: decisions/internals/an-agent-run-owned-by-another-instance-says-so.md
|
|
988
965
|
if (!stopped && run.status === 'running') {
|
|
989
966
|
logger?.warn(
|
|
990
967
|
`Could not interrupt run ${input.runId}: it is running in another process. ` +
|
|
@@ -1037,10 +1014,18 @@ export async function resumeAIAgent(
|
|
|
1037
1014
|
|
|
1038
1015
|
await assertAgentAuthorized(agent, params, packageName)
|
|
1039
1016
|
|
|
1040
|
-
|
|
1017
|
+
// The read above is not a claim — concurrent resumes all see the same pending
|
|
1018
|
+
// approval. `resolveApproval` is the claim, and the loser must not go on to
|
|
1019
|
+
// run the tool a second time.
|
|
1020
|
+
const claimed = await aiRunState.resolveApproval(
|
|
1041
1021
|
input.toolCallId,
|
|
1042
1022
|
input.approved ? 'approved' : 'denied'
|
|
1043
1023
|
)
|
|
1024
|
+
if (!claimed) {
|
|
1025
|
+
throw new Error(
|
|
1026
|
+
`Approval for toolCallId ${input.toolCallId} was already resolved by another caller`
|
|
1027
|
+
)
|
|
1028
|
+
}
|
|
1044
1029
|
|
|
1045
1030
|
const { storage } = resolveMemoryServices(agent, singletonServices)
|
|
1046
1031
|
const memoryConfig = agent.memory
|
|
@@ -1317,24 +1302,15 @@ async function continueAfterToolResult(
|
|
|
1317
1302
|
// One bag per run, shared by every middleware — see PikkuAIMiddlewareHooks.
|
|
1318
1303
|
const sharedNotes: Record<string, unknown> = {}
|
|
1319
1304
|
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
shared: sharedNotes,
|
|
1328
|
-
})
|
|
1329
|
-
modifiedMessages = result.messages
|
|
1330
|
-
modifiedInstructions = result.instructions
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1305
|
+
const { messages: modifiedMessages, instructions: modifiedInstructions } =
|
|
1306
|
+
await applyInputMiddleware(
|
|
1307
|
+
aiMiddlewares,
|
|
1308
|
+
singletonServices,
|
|
1309
|
+
{ messages: trimmedMessages, instructions: instructions },
|
|
1310
|
+
sharedNotes
|
|
1311
|
+
)
|
|
1333
1312
|
|
|
1334
|
-
//
|
|
1335
|
-
// listening to the same voice, and after an approval it is where most of the
|
|
1336
|
-
// reply actually gets spoken — an approved delete is followed by the agent
|
|
1337
|
-
// talking about it, and that is a normal thing to talk over.
|
|
1313
|
+
// knowledge: decisions/internals/a-resumed-agent-turn-is-as-interruptible-as-the-first.md
|
|
1338
1314
|
const interruptHandle = registerInterruptibleRun(run.runId)
|
|
1339
1315
|
|
|
1340
1316
|
const streamMiddleware = aiMiddlewares
|
|
@@ -1369,7 +1345,7 @@ async function continueAfterToolResult(
|
|
|
1369
1345
|
{
|
|
1370
1346
|
wireInheritedChannelMiddleware: meta?.channelMiddleware,
|
|
1371
1347
|
wireChannelMiddleware: [
|
|
1372
|
-
...(
|
|
1348
|
+
...(agent.channelMiddleware ?? []),
|
|
1373
1349
|
...streamMiddleware,
|
|
1374
1350
|
],
|
|
1375
1351
|
}
|
|
@@ -1467,10 +1443,7 @@ async function continueAfterToolResult(
|
|
|
1467
1443
|
run.runId
|
|
1468
1444
|
)
|
|
1469
1445
|
|
|
1470
|
-
//
|
|
1471
|
-
// matters more here. After an approval, most of what gets spoken is the
|
|
1472
|
-
// agent talking about what it just did, so this is the half of the reply a
|
|
1473
|
-
// dropped flush would silence.
|
|
1446
|
+
// knowledge: decisions/internals/the-agent-done-event-goes-through-the-middleware-and-is-awaited.md
|
|
1474
1447
|
await wrappedChannel.send({ type: 'done' })
|
|
1475
1448
|
channel.close()
|
|
1476
1449
|
} catch (err) {
|