@pikku/core 0.12.141 → 0.12.142
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 +33 -0
- package/dist/errors/errors.d.ts +1 -1
- package/dist/errors/errors.js +1 -1
- package/dist/function/function-meta.types.d.ts +6 -6
- package/dist/function/functions.types.d.ts +2 -2
- package/dist/services/audit-service.d.ts +4 -4
- package/dist/services/feature-flag-service.d.ts +3 -3
- package/dist/services/{http-personas.d.ts → http-virtual-users.d.ts} +25 -47
- package/dist/services/{http-personas.js → http-virtual-users.js} +29 -53
- package/dist/services/index.d.ts +2 -2
- package/dist/services/index.js +1 -1
- package/dist/services/meta-service.d.ts +6 -6
- package/dist/services/meta-service.js +8 -8
- package/dist/{wirings/workflow → services}/scenario-cookie-jar.d.ts +2 -0
- package/dist/{wirings/workflow → services}/scenario-cookie-jar.js +18 -14
- package/dist/services/system-role-guard.d.ts +1 -1
- package/dist/services/virtual-user-sign-in.d.ts +52 -0
- package/dist/services/virtual-user-sign-in.js +80 -0
- package/dist/services/virtual-user-statement.d.ts +31 -0
- package/dist/services/virtual-user-statement.js +24 -0
- package/dist/services/{personas-service.d.ts → virtual-users-service.d.ts} +25 -23
- package/dist/services/{personas-service.js → virtual-users-service.js} +3 -3
- package/dist/types/core.types.d.ts +11 -11
- package/dist/wirings/role/define-system-role.d.ts +1 -1
- package/dist/wirings/role/define-system-role.js +1 -1
- package/dist/wirings/{actor-flow/actor-flow.types.d.ts → virtual-user/conversation/conversation.types.d.ts} +5 -5
- package/dist/wirings/virtual-user/conversation/index.d.ts +2 -0
- package/dist/wirings/virtual-user/conversation/run-conversation.d.ts +36 -0
- package/dist/wirings/{actor-flow → virtual-user/conversation}/run-conversation.js +30 -30
- package/dist/wirings/virtual-user/define-virtual-users.d.ts +32 -0
- package/dist/wirings/{persona/define-personas.js → virtual-user/define-virtual-users.js} +9 -9
- package/dist/wirings/virtual-user/index.d.ts +30 -7
- package/dist/wirings/virtual-user/index.js +23 -4
- package/dist/wirings/virtual-user/prepare-virtual-user-run.d.ts +8 -8
- package/dist/wirings/virtual-user/prepare-virtual-user-run.js +9 -9
- package/dist/wirings/virtual-user/run-virtual-user.d.ts +6 -6
- package/dist/wirings/virtual-user/run-virtual-user.js +10 -10
- package/dist/wirings/{persona/validate-personas.d.ts → virtual-user/validate-virtual-users.d.ts} +11 -11
- package/dist/wirings/{persona/validate-personas.js → virtual-user/validate-virtual-users.js} +24 -24
- package/dist/wirings/virtual-user/virtual-user-agents.d.ts +7 -7
- package/dist/wirings/virtual-user/virtual-user-agents.js +2 -2
- package/dist/wirings/{persona/persona-app-scopes.d.ts → virtual-user/virtual-user-app-scopes.d.ts} +10 -10
- package/dist/wirings/{persona/persona-app-scopes.js → virtual-user/virtual-user-app-scopes.js} +12 -12
- package/dist/wirings/virtual-user/virtual-user-catalogue.d.ts +3 -3
- package/dist/wirings/virtual-user/virtual-user-catalogue.js +2 -2
- package/dist/wirings/{persona/persona.types.d.ts → virtual-user/virtual-user-declaration.types.d.ts} +24 -24
- package/dist/wirings/virtual-user/virtual-user-derive.js +13 -7
- package/dist/wirings/virtual-user/virtual-user-dispositions.d.ts +1 -1
- package/dist/wirings/{persona/persona-email.d.ts → virtual-user/virtual-user-email.d.ts} +11 -11
- package/dist/wirings/{persona/persona-email.js → virtual-user/virtual-user-email.js} +19 -19
- package/dist/wirings/{persona/persona-environments.d.ts → virtual-user/virtual-user-environments.d.ts} +10 -10
- package/dist/wirings/{persona/persona-environments.js → virtual-user/virtual-user-environments.js} +21 -21
- package/dist/wirings/virtual-user/virtual-user-intents.d.ts +3 -3
- package/dist/wirings/virtual-user/virtual-user-intents.js +3 -3
- package/dist/wirings/{persona/persona-mailbox.d.ts → virtual-user/virtual-user-mailbox.d.ts} +4 -4
- package/dist/wirings/virtual-user/virtual-user-run-store.d.ts +4 -4
- package/dist/wirings/virtual-user/virtual-user-scaffold.d.ts +42 -61
- package/dist/wirings/virtual-user/virtual-user-scaffold.js +69 -117
- package/dist/wirings/virtual-user/virtual-user-schedule-store.d.ts +15 -15
- package/dist/wirings/virtual-user/virtual-user-schedule.d.ts +6 -6
- package/dist/wirings/virtual-user/virtual-user-schedule.js +11 -11
- package/dist/wirings/virtual-user/virtual-user-target.d.ts +6 -6
- package/dist/wirings/virtual-user/virtual-user-target.js +5 -5
- package/dist/wirings/virtual-user/virtual-user.types.d.ts +11 -11
- package/dist/wirings/virtual-user/virtual-user.types.js +1 -1
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +9 -9
- package/dist/wirings/workflow/pikku-scenario-service.d.ts +28 -28
- package/dist/wirings/workflow/pikku-scenario-service.js +77 -77
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +2 -2
- package/dist/wirings/workflow/pikku-workflow-service.js +4 -4
- package/dist/wirings/workflow/scenario-prose.d.ts +8 -8
- package/dist/wirings/workflow/scenario-prose.js +12 -8
- package/dist/wirings/workflow/scenario-run.types.d.ts +13 -13
- package/dist/wirings/workflow/scenario-step.types.d.ts +23 -23
- package/dist/wirings/workflow/workflow.types.d.ts +2 -2
- package/knowledge/decisions/index.md +0 -2
- package/knowledge/decisions/internals/a-credential-name-declared-twice-must-agree-on-schema-and-type.md +29 -0
- package/knowledge/decisions/internals/a-grant-can-name-a-scope-declared-without-its-addon.md +29 -0
- package/knowledge/decisions/internals/a-scheduled-task-writes-its-caller-session-before-it-runs.md +22 -0
- package/knowledge/decisions/internals/a-secret-set-in-memory-shadows-the-variable-of-the-same-name.md +28 -0
- package/knowledge/decisions/internals/a-virtual-user-acts-through-invokeraw-so-a-refusal-is-data.md +32 -0
- package/knowledge/decisions/internals/a-virtual-user-cadence-is-a-row-not-a-timer.md +12 -12
- package/knowledge/decisions/internals/a-virtual-user-converse-signs-in-before-its-first-turn.md +24 -0
- package/knowledge/decisions/internals/a-virtual-user-email-is-computed-with-the-run-id-as-a-sub-address.md +34 -0
- package/knowledge/decisions/internals/a-virtual-user-run-is-not-a-workflow-but-it-needs-a-trigger.md +2 -2
- package/knowledge/decisions/internals/an-agent-is-offered-under-its-registry-key-and-only-within-the-virtual-users-scopes.md +27 -0
- package/knowledge/decisions/internals/an-oauth2-credential-implies-an-optional-app-secret.md +26 -0
- package/knowledge/decisions/internals/analytics-identity-resolvers-run-in-declared-order.md +22 -0
- package/knowledge/decisions/internals/feature-flag-declarations-refuse-an-empty-anyof-and-conflicting-scopes.md +22 -0
- package/knowledge/decisions/internals/feature-flags-fail-open-and-never-enforce-access.md +28 -0
- package/knowledge/decisions/internals/flag-sync-rewrites-only-the-declared-text.md +24 -0
- package/knowledge/decisions/internals/in-a-scenario-a-4xx-is-data-not-an-exception.md +3 -3
- package/knowledge/decisions/internals/index.md +46 -31
- package/knowledge/decisions/internals/one-project-shape-check-two-validators.md +10 -10
- package/knowledge/decisions/internals/role-names-cannot-contain-the-scope-separator.md +30 -0
- package/knowledge/decisions/internals/scenario-agent-calls-sign-in-on-401-only.md +23 -19
- package/knowledge/decisions/internals/scenario-steps-return-drained-response-records.md +2 -2
- package/knowledge/decisions/internals/scenarios-live-in-files-named-for-them.md +12 -12
- package/knowledge/decisions/internals/the-core-scope-validator-refuses-separator-wildcard-and-empty-segments.md +22 -0
- package/knowledge/decisions/internals/the-pikku-user-id-resolves-in-a-fixed-order.md +24 -0
- package/knowledge/decisions/internals/{the-actor-prompt-says-json-because-of-json-object-mode.md → the-virtual-user-prompt-says-json-because-of-json-object-mode.md} +4 -4
- package/knowledge/decisions/internals/{the-persona-runtime-is-exported-from-the-persona-entry-point.md → the-virtual-user-runtime-is-exported-from-the-virtual-user-entry-point.md} +8 -8
- package/knowledge/decisions/internals/validate-checks-virtual-users-through-a-shared-module.md +43 -0
- package/knowledge/decisions/internals/{actor-flow-drives-the-target-through-a-transport-seam.md → virtual-user-conversation-drives-the-target-through-a-transport-seam.md} +6 -6
- package/knowledge/decisions/internals/virtual-user-conversation-verdicts-are-llm-self-evaluations.md +25 -0
- package/knowledge/decisions/internals/virtual-user-conversations-seed-a-hidden-kickoff-message.md +23 -0
- package/knowledge/decisions/internals/virtual-users-are-declared-in-one-definevirtualusers-call-per-codebase.md +30 -0
- package/knowledge/decisions/internals/workflow-core-never-imports-a-browser-driver.md +5 -5
- package/knowledge/decisions/internals/workflow-run-capabilities-are-extensions-not-subclasses.md +2 -2
- package/knowledge/decisions/internals/workflow-scenario-assertions-never-retry-and-record-one-step.md +4 -4
- package/knowledge/decisions/internals/workflow-scenario-hooks-are-a-scenario-only-affordance.md +1 -1
- package/knowledge/decisions/internals/workflow-scenario-prose-is-rendered-from-typed-calls-not-parsed-from-english.md +2 -2
- package/knowledge/decisions/internals/workflow-scenario-step-targets-are-string-literals-for-the-inspector.md +1 -1
- package/knowledge/decisions/internals/workflow-suspended-runs-keep-their-in-process-context.md +1 -1
- package/knowledge/decisions/security/a-bearer-token-checked-against-a-secret-reads-it-per-request.md +26 -0
- package/knowledge/decisions/security/a-call-without-a-session-raises-missing-session-not-forbidden.md +20 -0
- package/knowledge/decisions/security/a-caller-cookie-wins-over-the-jar-for-the-same-name.md +23 -0
- package/knowledge/decisions/security/a-channel-lifecycle-acts-as-the-socket-upgrade-session.md +16 -0
- package/knowledge/decisions/security/a-channel-requires-a-session-unless-it-says-auth-false.md +26 -0
- package/knowledge/decisions/security/a-credential-read-path-follows-its-declared-type-not-absence.md +32 -0
- package/knowledge/decisions/security/a-credential-type-is-a-literal-the-inspector-checks-at-declaration.md +27 -0
- package/knowledge/decisions/security/a-first-party-cookie-is-written-only-after-its-purposes-consent.md +33 -0
- package/knowledge/decisions/security/a-readonly-session-reaches-only-readonly-functions.md +25 -0
- package/knowledge/decisions/security/a-scaffold-flag-says-a-surface-exists-not-who-may-call-it.md +10 -5
- package/knowledge/decisions/security/a-scheduled-virtual-user-run-needs-no-admin-at-run-time.md +27 -0
- package/knowledge/decisions/security/a-scoped-credential-view-is-limited-to-declared-names.md +26 -0
- package/knowledge/decisions/security/a-scoped-secret-view-reads-only-declared-keys-and-never-writes.md +25 -0
- package/knowledge/decisions/security/a-secret-is-sent-only-to-the-hosts-it-declares.md +29 -0
- package/knowledge/decisions/security/a-step-can-name-an-identity-no-virtual-user-holds-through-per-call-headers.md +25 -0
- package/knowledge/decisions/security/a-stopped-run-by-id-skips-the-ownership-check.md +24 -0
- package/knowledge/decisions/security/a-vault-secret-redacts-on-serialization-and-throws-on-coercion.md +29 -0
- package/knowledge/decisions/security/a-virtual-user-catalogue-entry-is-filtered-by-scope-and-approval-is-denied-by-default.md +30 -0
- package/knowledge/decisions/security/a-virtual-user-environment-fails-closed.md +38 -0
- package/knowledge/decisions/security/a-virtual-user-never-runs-against-production.md +23 -0
- package/knowledge/decisions/security/a-virtual-user-reads-its-roles-from-the-apps-own-rpc-before-better-auth.md +27 -0
- package/knowledge/decisions/security/a-virtual-user-run-treats-an-unresolved-environment-as-production.md +41 -0
- package/knowledge/decisions/security/a-virtual-user-scopes-are-expanded-from-its-roles-once-at-run-preparation.md +25 -0
- package/knowledge/decisions/security/a-virtual-user-whose-roles-drift-from-the-declaration-refuses-to-run.md +32 -0
- package/knowledge/decisions/security/addon-screen-scopes-hide-and-do-not-enforce.md +20 -0
- package/knowledge/decisions/security/an-addon-header-can-only-narrow-the-session.md +26 -0
- package/knowledge/decisions/security/an-addon-instance-reads-only-the-secrets-and-credentials-it-is-named.md +33 -0
- package/knowledge/decisions/security/an-addon-may-reach-only-itself-and-the-addons-it-uses.md +16 -0
- package/knowledge/decisions/security/an-api-key-is-jwt-decoded-into-the-session.md +26 -0
- package/knowledge/decisions/security/an-external-virtual-user-caller-presents-one-signed-statement.md +38 -0
- package/knowledge/decisions/security/an-inbound-webhook-source-dispatches-only-verified-requests.md +31 -0
- package/knowledge/decisions/security/an-mcp-call-with-no-http-request-stays-anonymous.md +18 -0
- package/knowledge/decisions/security/an-omitted-user-id-addresses-the-platform-credential.md +32 -0
- package/knowledge/decisions/security/analytics-identity-is-stamped-from-the-session-not-the-event.md +24 -0
- package/knowledge/decisions/security/app-access-is-a-scope-tree-synthesised-from-virtual-users.md +26 -0
- package/knowledge/decisions/security/approval-policy-decides-who-answers-a-tool-call.md +27 -0
- package/knowledge/decisions/security/auth-false-on-a-session-function-warns-and-still-requires-one.md +25 -0
- package/knowledge/decisions/security/bearer-token-hashes-are-unpeppered-sha256.md +18 -0
- package/knowledge/decisions/security/consent-is-read-from-a-cookie-and-a-recognised-denial-counts-as-refusal.md +29 -0
- package/knowledge/decisions/security/cors-defaults-to-any-origin-and-refuses-the-wildcard-with-credentials.md +22 -0
- package/knowledge/decisions/security/credential-backends-share-one-conformance-suite-for-per-user-isolation.md +29 -0
- package/knowledge/decisions/security/each-stage-has-one-virtual-user-key-pair.md +27 -0
- package/knowledge/decisions/security/hmac-verification-fails-closed-on-empty-inputs.md +25 -0
- package/knowledge/decisions/security/index.md +88 -24
- package/knowledge/decisions/security/local-content-refuses-private-buckets-under-the-public-segment.md +21 -0
- package/knowledge/decisions/security/mail-is-allowlisted-before-a-virtual-user-run-sees-it.md +38 -0
- package/knowledge/decisions/security/mcp-resources-tools-and-prompts-take-sessionless-functions.md +18 -0
- package/knowledge/decisions/security/origin-locked-routes-match-the-origin-exactly.md +22 -0
- package/knowledge/decisions/security/oss-auth-works-without-pikku-studio-cloud.md +26 -0
- package/knowledge/decisions/security/outbound-webhooks-use-an-allowlist-when-configured.md +24 -0
- package/knowledge/decisions/security/pikku-carries-virtual-user-scopes-as-data-and-the-app-grants-them.md +35 -0
- package/knowledge/decisions/security/scoped-content-access-is-a-root-folder-with-longest-prefix-grants.md +31 -0
- package/knowledge/decisions/security/services-without-secrets-get-a-throwing-secrets-accessor.md +22 -0
- package/knowledge/decisions/security/shared-guards-refuse-to-mutate-or-shadow-a-system-role.md +34 -0
- package/knowledge/decisions/security/signed-uploads-are-verified-by-the-same-check-as-signed-reads.md +26 -0
- package/knowledge/decisions/security/system-role-scopes-must-be-declared-with-define-scope.md +24 -0
- package/knowledge/decisions/security/system-roles-are-additive-and-immutable.md +16 -0
- package/knowledge/decisions/security/the-channel-rpc-does-not-send-the-callers-session.md +21 -0
- package/knowledge/decisions/security/the-remote-jobs-inbox-fails-closed-without-a-secret.md +24 -0
- package/knowledge/decisions/security/the-remote-rpc-gate-fails-closed-and-binds-the-token-to-its-function.md +34 -0
- package/knowledge/decisions/security/the-workflow-status-stream-rechecks-ownership-on-every-poll.md +16 -0
- package/knowledge/decisions/security/two-credentials-on-one-key-must-share-a-read-mode.md +29 -0
- package/knowledge/decisions/security/verified-bearer-claims-are-pass-through-data-that-core-never-fills.md +23 -0
- package/knowledge/decisions/security/virtual-user-catalogue-excludes-the-machinery-that-runs-virtual-users.md +25 -0
- package/knowledge/decisions/security/{actor-flow-missing-approval-decisions-default-to-denied.md → virtual-user-conversation-missing-approval-decisions-default-to-denied.md} +7 -7
- package/knowledge/decisions/security/virtual-user-run-records-are-gated-behind-a-read-scope.md +18 -0
- package/knowledge/decisions/security/virtual-user-sign-in-does-not-exist-in-production.md +25 -0
- package/knowledge/decisions/security/virtual-user-sign-in-has-one-door.md +42 -0
- package/knowledge/decisions/security/{actor-sign-in-is-proven-by-set-cookie-not-a-non-empty-jar.md → virtual-user-sign-in-is-proven-by-set-cookie-not-a-non-empty-jar.md} +10 -8
- package/knowledge/decisions/security/virtual-user-sign-in-only-works-for-virtual-user-flagged-users.md +29 -0
- package/knowledge/decisions/security/workflow-helper-functions-are-sessionless-by-declaration.md +18 -0
- package/knowledge/decisions/security/{workflow-scenario-sessions-are-isolated-per-actor-and-per-scenario.md → workflow-scenario-sessions-are-isolated-per-virtual-user-and-per-scenario.md} +6 -6
- package/knowledge/decisions/security/workflow-scenario-steps-are-never-network-invocable.md +5 -5
- package/knowledge/decisions/security/{workflow-actor-steps-always-use-the-real-transport.md → workflow-virtual-user-steps-always-use-the-real-transport.md} +11 -11
- package/knowledge/decisions/security/writing-a-virtual-user-schedule-is-gated-by-its-own-scope.md +18 -0
- package/knowledge/index.md +0 -2
- package/knowledge/questions/a-bearer-jwt-with-no-identity-becomes-a-session.md +24 -0
- package/knowledge/questions/dev-quick-login-as-impersonation.md +25 -0
- package/knowledge/questions/does-a-delayed-rpc-run-as-the-session-it-was-scheduled-with.md +26 -0
- package/knowledge/questions/does-a-user-id-with-a-colon-read-another-users-credentials.md +27 -0
- package/knowledge/questions/does-a-wirecli-command-need-a-session-by-default.md +26 -0
- package/knowledge/questions/does-an-adversarial-in-scope-success-count-as-a-finding.md +26 -0
- package/knowledge/questions/does-approve-step-need-the-run-owners-session-or-the-gates-approvers.md +34 -0
- package/knowledge/questions/does-cors-origin-true-reflect-any-origin-with-credentials.md +20 -0
- package/knowledge/questions/does-skip-user-session-do-anything-on-a-channel.md +24 -0
- package/knowledge/questions/does-the-store-session-match-the-other-two.md +19 -0
- package/knowledge/questions/index.md +25 -2
- package/knowledge/questions/is-the-agent-score-worker-reachable-without-a-session.md +21 -0
- package/knowledge/questions/live-scheduled-tests-may-write.md +19 -0
- package/knowledge/questions/provision-virtual-users-has-no-caller-in-this-repo.md +23 -0
- package/knowledge/questions/remote-secret-refuses-requests-without-a-remote-token-on-every-path.md +32 -0
- package/knowledge/questions/should-a-middleware-set-session-persist-to-the-store.md +25 -0
- package/knowledge/questions/should-a-scoped-secret-view-throw-from-has-secret-for-a-disallowed-key.md +23 -0
- package/knowledge/questions/should-a-wrong-bearer-token-be-anonymous-or-refused.md +35 -0
- package/knowledge/questions/should-an-empty-webhook-signing-secret-send-unsigned.md +23 -0
- package/knowledge/questions/should-http-virtual-user-sessions-reset-between-scenarios-in-one-run.md +27 -0
- package/knowledge/questions/should-the-cookie-jar-scope-cookies-to-the-api-origin.md +26 -0
- package/knowledge/questions/should-voice-input-fetch-audio-urls-without-a-host-allowlist.md +20 -0
- package/knowledge/questions/staging-impersonation-rules.md +27 -0
- package/knowledge/questions/virtual-user-keys-on-cloud-stages-are-pikku-studios-to-build.md +34 -0
- package/knowledge/questions/virtual-user-statement-replay-is-per-process.md +20 -0
- package/knowledge/questions/what-an-impersonated-session-carries.md +22 -0
- package/package.json +2 -3
- package/src/public-surface.json +38 -47
- package/dist/services/persona-actor-secret.d.ts +0 -38
- package/dist/services/persona-actor-secret.js +0 -39
- package/dist/services/persona-sign-in.d.ts +0 -117
- package/dist/services/persona-sign-in.js +0 -154
- package/dist/wirings/actor-flow/index.d.ts +0 -2
- package/dist/wirings/actor-flow/run-conversation.d.ts +0 -36
- package/dist/wirings/persona/define-personas.d.ts +0 -32
- package/dist/wirings/persona/index.d.ts +0 -23
- package/dist/wirings/persona/index.js +0 -19
- package/knowledge/decisions/internals/actor-flow-conversations-seed-a-hidden-kickoff-message.md +0 -23
- package/knowledge/decisions/internals/actor-flow-verdicts-are-llm-self-evaluations.md +0 -25
- package/knowledge/decisions/internals/validate-checks-personas-through-a-shared-module.md +0 -43
- package/knowledge/decisions/security/actor-sign-in-only-works-for-actor-flagged-users.md +0 -31
- package/knowledge/decisions/security/an-actor-credential-is-derived-per-persona.md +0 -42
- package/knowledge/decisions/security/pikku-carries-actor-scopes-as-data-and-the-app-grants-them.md +0 -26
- /package/dist/wirings/{actor-flow/actor-flow.types.js → virtual-user/conversation/conversation.types.js} +0 -0
- /package/dist/wirings/{actor-flow → virtual-user/conversation}/index.js +0 -0
- /package/dist/wirings/{persona/persona.types.js → virtual-user/virtual-user-declaration.types.js} +0 -0
- /package/dist/wirings/{persona/persona-mailbox.js → virtual-user/virtual-user-mailbox.js} +0 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ResolvedVirtualUser } from './virtual-users-service.js';
|
|
2
|
+
import type { ScenarioCookieJar } from './scenario-cookie-jar.js';
|
|
3
|
+
/**
|
|
4
|
+
* How a virtual user obtains a session on the target. Both implementations end
|
|
5
|
+
* in the app's one virtual user sign-in handler; they differ in how they get
|
|
6
|
+
* there. See docs/virtual-user-sign-in.md.
|
|
7
|
+
*/
|
|
8
|
+
export interface VirtualUserSignIn {
|
|
9
|
+
/**
|
|
10
|
+
* Establish a session in `jar`. Throws on failure with a message naming the
|
|
11
|
+
* virtual user, since a run that continues unauthenticated fails later and
|
|
12
|
+
* somewhere less informative.
|
|
13
|
+
*/
|
|
14
|
+
login(jar: ScenarioCookieJar, virtualUser: ResolvedVirtualUser): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The auth mount a configured sign-in path sits under, or `undefined` when it
|
|
18
|
+
* names nothing recognisable.
|
|
19
|
+
*
|
|
20
|
+
* better-auth serves sign-in and `get-session` from one prefix, so an app that
|
|
21
|
+
* mounts it at `/api/auth` moves both together and says so once through
|
|
22
|
+
* `signInPath`. Reading `get-session` from a hardcoded `/auth` on such an app
|
|
23
|
+
* 404s, and a 404 reads as "this stage does not report roles", which silently
|
|
24
|
+
* turns off the check that tells a permissions finding from seed drift.
|
|
25
|
+
*/
|
|
26
|
+
export declare const authMount: (signInPath?: string) => string | undefined;
|
|
27
|
+
/** Signs one statement naming `virtualUser`, for an audience the signer knows. */
|
|
28
|
+
export type VirtualUserStatementMint = (virtualUser: ResolvedVirtualUser) => string | Promise<string>;
|
|
29
|
+
export interface StatementSignInOptions {
|
|
30
|
+
apiUrl: string;
|
|
31
|
+
/** Default `/auth/sign-in/virtual-user`. */
|
|
32
|
+
signInPath?: string;
|
|
33
|
+
mint: VirtualUserStatementMint;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Sign in from outside the app by presenting a signed statement.
|
|
37
|
+
*
|
|
38
|
+
* A fresh statement is minted per login, because each is single-use: the re-login
|
|
39
|
+
* after a 401 must not replay the first.
|
|
40
|
+
*/
|
|
41
|
+
export declare const statementSignIn: ({ apiUrl, signInPath, mint, }: StatementSignInOptions) => VirtualUserSignIn;
|
|
42
|
+
/** The part of an auth instance an in-process sign-in needs. */
|
|
43
|
+
export interface InProcessAuth {
|
|
44
|
+
api: Record<string, any>;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Sign in from inside the app, with no credential: the code asking is the app.
|
|
48
|
+
*
|
|
49
|
+
* Calls the server-only endpoint through `auth.api`, which the HTTP router
|
|
50
|
+
* never serves, and takes the session cookie from the response.
|
|
51
|
+
*/
|
|
52
|
+
export declare const inProcessSignIn: (auth: InProcessAuth) => VirtualUserSignIn;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { PikkuError } from '../errors/error-handler.js';
|
|
2
|
+
import { VIRTUAL_USER_IN_PROCESS_ENDPOINT } from './virtual-user-statement.js';
|
|
3
|
+
/**
|
|
4
|
+
* A sign-in the target refused. `PikkuError`, not `Error`, so the CLI prints
|
|
5
|
+
* this message alone: a rejected statement or a virtual user the stage has never
|
|
6
|
+
* seen is something to go and fix, and a stack trace through the fetch internals
|
|
7
|
+
* only buries the status and the body that say which one it is.
|
|
8
|
+
*/
|
|
9
|
+
const failed = async (what, virtualUserId, res) => {
|
|
10
|
+
const body = (await res.text().catch(() => '')).slice(0, 300);
|
|
11
|
+
return new PikkuError(`[scenario] ${what} failed for '${virtualUserId}' (${res.status}): ${body}`);
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* The auth mount a configured sign-in path sits under, or `undefined` when it
|
|
15
|
+
* names nothing recognisable.
|
|
16
|
+
*
|
|
17
|
+
* better-auth serves sign-in and `get-session` from one prefix, so an app that
|
|
18
|
+
* mounts it at `/api/auth` moves both together and says so once through
|
|
19
|
+
* `signInPath`. Reading `get-session` from a hardcoded `/auth` on such an app
|
|
20
|
+
* 404s, and a 404 reads as "this stage does not report roles", which silently
|
|
21
|
+
* turns off the check that tells a permissions finding from seed drift.
|
|
22
|
+
*/
|
|
23
|
+
export const authMount = (signInPath) => {
|
|
24
|
+
const mount = signInPath ? signInPath.lastIndexOf('/sign-in/') : -1;
|
|
25
|
+
return !signInPath || mount === -1 ? undefined : signInPath.slice(0, mount);
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Sign in from outside the app by presenting a signed statement.
|
|
29
|
+
*
|
|
30
|
+
* A fresh statement is minted per login, because each is single-use: the re-login
|
|
31
|
+
* after a 401 must not replay the first.
|
|
32
|
+
*/
|
|
33
|
+
export const statementSignIn = ({ apiUrl, signInPath = '/auth/sign-in/virtual-user', mint, }) => ({
|
|
34
|
+
async login(jar, virtualUser) {
|
|
35
|
+
const res = await jar.fetch(`${apiUrl}${signInPath}`, {
|
|
36
|
+
method: 'POST',
|
|
37
|
+
headers: { 'content-type': 'application/json' },
|
|
38
|
+
body: JSON.stringify({ statement: await mint(virtualUser) }),
|
|
39
|
+
});
|
|
40
|
+
if (res.status === 404) {
|
|
41
|
+
throw new PikkuError(`[scenario] ${apiUrl}${signInPath} does not exist, so '${virtualUser.id}' cannot sign in. ` +
|
|
42
|
+
`The target has no VIRTUAL_USER_VERIFY_KEY set, or does not serve virtual user sign-in at all (production never does).`);
|
|
43
|
+
}
|
|
44
|
+
if (!res.ok) {
|
|
45
|
+
throw await failed('virtual user sign-in', virtualUser.id, res);
|
|
46
|
+
}
|
|
47
|
+
// What proves a session was established is this response setting a cookie,
|
|
48
|
+
// not the jar being non-empty: the target may have set one earlier.
|
|
49
|
+
if (res.headers.getSetCookie().length === 0) {
|
|
50
|
+
throw new PikkuError(`[scenario] virtual user sign-in for '${virtualUser.id}' returned no session cookie`);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
/**
|
|
55
|
+
* Sign in from inside the app, with no credential: the code asking is the app.
|
|
56
|
+
*
|
|
57
|
+
* Calls the server-only endpoint through `auth.api`, which the HTTP router
|
|
58
|
+
* never serves, and takes the session cookie from the response.
|
|
59
|
+
*/
|
|
60
|
+
export const inProcessSignIn = (auth) => ({
|
|
61
|
+
async login(jar, virtualUser) {
|
|
62
|
+
const endpoint = auth.api[VIRTUAL_USER_IN_PROCESS_ENDPOINT];
|
|
63
|
+
if (typeof endpoint !== 'function') {
|
|
64
|
+
throw new PikkuError(`[scenario] '${virtualUser.id}' cannot sign in: the app's auth has no virtual user sign-in. ` +
|
|
65
|
+
`Add the pikkuVirtualUser plugin from @pikku/better-auth, and note it is only registered under pikku dev or the stage opt-in.`);
|
|
66
|
+
}
|
|
67
|
+
const res = await endpoint({
|
|
68
|
+
body: { email: virtualUser.email, name: virtualUser.name },
|
|
69
|
+
asResponse: true,
|
|
70
|
+
});
|
|
71
|
+
if (!res.ok) {
|
|
72
|
+
throw await failed('virtual user sign-in', virtualUser.id, res);
|
|
73
|
+
}
|
|
74
|
+
const cookies = res.headers.getSetCookie();
|
|
75
|
+
if (cookies.length === 0) {
|
|
76
|
+
throw new PikkuError(`[scenario] virtual user sign-in for '${virtualUser.id}' returned no session cookie`);
|
|
77
|
+
}
|
|
78
|
+
jar.absorb(cookies);
|
|
79
|
+
},
|
|
80
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The signed statement an external caller presents to sign a virtual user in.
|
|
3
|
+
*
|
|
4
|
+
* An EdDSA (Ed25519) JWT: `sub` is the virtual user id, `aud` the app's
|
|
5
|
+
* audience, `exp` a few minutes out and `jti` a one-time id. The app can only
|
|
6
|
+
* verify one, against a public key; whoever deploys holds the private key.
|
|
7
|
+
*/
|
|
8
|
+
export declare const VIRTUAL_USER_STATEMENT_ALGORITHM = "EdDSA";
|
|
9
|
+
/** How long a statement lives when its signer does not say. */
|
|
10
|
+
export declare const VIRTUAL_USER_STATEMENT_TTL_SECONDS = 180;
|
|
11
|
+
/** The longest a verifier accepts, whatever `exp` claims. */
|
|
12
|
+
export declare const VIRTUAL_USER_STATEMENT_MAX_TTL_SECONDS = 300;
|
|
13
|
+
/** The variable a stage carries its PUBLIC key in. Not `PIKKU_`/`PIKKUSTUDIO_`: a stage rejects those. */
|
|
14
|
+
export declare const VIRTUAL_USER_VERIFY_KEY_VARIABLE = "VIRTUAL_USER_VERIFY_KEY";
|
|
15
|
+
/** The variable whoever deploys holds the PRIVATE key in. Never set on a stage. */
|
|
16
|
+
export declare const VIRTUAL_USER_SIGNING_KEY_VARIABLE = "VIRTUAL_USER_SIGNING_KEY";
|
|
17
|
+
/** The key a run reaches the in-process sign-in under, on `auth.api`. */
|
|
18
|
+
export declare const VIRTUAL_USER_IN_PROCESS_ENDPOINT = "signInVirtualUserInternal";
|
|
19
|
+
export interface VirtualUserStatementVerifier {
|
|
20
|
+
/**
|
|
21
|
+
* Resolves to the virtual user the statement names. Throws
|
|
22
|
+
* {@link VirtualUserStatementError} for anything that is not a fresh,
|
|
23
|
+
* unreplayed statement for `audience` signed by the private half of the key.
|
|
24
|
+
*/
|
|
25
|
+
verify(statement: string, audience: string): Promise<{
|
|
26
|
+
virtualUserId: string;
|
|
27
|
+
}>;
|
|
28
|
+
}
|
|
29
|
+
export declare class VirtualUserStatementError extends Error {
|
|
30
|
+
constructor(message: string);
|
|
31
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The signed statement an external caller presents to sign a virtual user in.
|
|
3
|
+
*
|
|
4
|
+
* An EdDSA (Ed25519) JWT: `sub` is the virtual user id, `aud` the app's
|
|
5
|
+
* audience, `exp` a few minutes out and `jti` a one-time id. The app can only
|
|
6
|
+
* verify one, against a public key; whoever deploys holds the private key.
|
|
7
|
+
*/
|
|
8
|
+
export const VIRTUAL_USER_STATEMENT_ALGORITHM = 'EdDSA';
|
|
9
|
+
/** How long a statement lives when its signer does not say. */
|
|
10
|
+
export const VIRTUAL_USER_STATEMENT_TTL_SECONDS = 180;
|
|
11
|
+
/** The longest a verifier accepts, whatever `exp` claims. */
|
|
12
|
+
export const VIRTUAL_USER_STATEMENT_MAX_TTL_SECONDS = 300;
|
|
13
|
+
/** The variable a stage carries its PUBLIC key in. Not `PIKKU_`/`PIKKUSTUDIO_`: a stage rejects those. */
|
|
14
|
+
export const VIRTUAL_USER_VERIFY_KEY_VARIABLE = 'VIRTUAL_USER_VERIFY_KEY';
|
|
15
|
+
/** The variable whoever deploys holds the PRIVATE key in. Never set on a stage. */
|
|
16
|
+
export const VIRTUAL_USER_SIGNING_KEY_VARIABLE = 'VIRTUAL_USER_SIGNING_KEY';
|
|
17
|
+
/** The key a run reaches the in-process sign-in under, on `auth.api`. */
|
|
18
|
+
export const VIRTUAL_USER_IN_PROCESS_ENDPOINT = 'signInVirtualUserInternal';
|
|
19
|
+
export class VirtualUserStatementError extends Error {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = 'VirtualUserStatementError';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ConverseOptions,
|
|
2
|
-
import type {
|
|
1
|
+
import type { ConverseOptions, ConversationVerdict } from '../wirings/virtual-user/conversation/conversation.types.js';
|
|
2
|
+
import type { VirtualUserMeta } from '../wirings/virtual-user/virtual-user-declaration.types.js';
|
|
3
3
|
/**
|
|
4
4
|
* What the transport answered, for a step that treats the status as data.
|
|
5
5
|
*
|
|
@@ -30,8 +30,8 @@ export interface ScenarioHttpResponse<T = unknown> {
|
|
|
30
30
|
* Drain a response into the shape a step can carry: the parsed body (an empty
|
|
31
31
|
* one counting as no body at all) alongside the text it was parsed from.
|
|
32
32
|
*
|
|
33
|
-
* `invokeRaw` returns this, and a step that has to reach past a
|
|
34
|
-
* route with no RPC, an identity no
|
|
33
|
+
* `invokeRaw` returns this, and a step that has to reach past a virtual user — a
|
|
34
|
+
* route with no RPC, an identity no virtual user can hold — reaches for this rather
|
|
35
35
|
* than writing the same record by hand.
|
|
36
36
|
*
|
|
37
37
|
* @example snippet: scenarioPolling
|
|
@@ -55,7 +55,7 @@ export interface ScenarioJsonRequest {
|
|
|
55
55
|
/**
|
|
56
56
|
* POST JSON somewhere and report what came back, without throwing on a 4xx/5xx.
|
|
57
57
|
*
|
|
58
|
-
* Every scenario that reaches past a
|
|
58
|
+
* Every scenario that reaches past a virtual user was writing this by hand — the same
|
|
59
59
|
* `content-type`, the same `JSON.stringify`, the same drain — and the copies had
|
|
60
60
|
* drifted: some returned `res.json()`, which loses the status and throws
|
|
61
61
|
* outright when the target answers an empty body or an HTML error page. A
|
|
@@ -68,36 +68,38 @@ export declare const postScenarioJson: <T = unknown>(url: string, { body, header
|
|
|
68
68
|
/** Per-call transport options. */
|
|
69
69
|
export interface ScenarioInvokeOptions {
|
|
70
70
|
/**
|
|
71
|
-
* Headers to send alongside the
|
|
72
|
-
* expresses an identity no declared
|
|
71
|
+
* Headers to send alongside the virtual user's own session. This is how a step
|
|
72
|
+
* expresses an identity no declared virtual user can — an impersonation header, or
|
|
73
73
|
* one of the header-shim principals a credential scenario invents.
|
|
74
74
|
*/
|
|
75
75
|
headers?: Record<string, string>;
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
|
-
* The RPC surface a
|
|
78
|
+
* The RPC surface a virtual user can reach, as name → input/output. A project binds
|
|
79
79
|
* its generated exposed RPC map here; the default leaves every name open, which
|
|
80
|
-
* is what a
|
|
80
|
+
* is what a virtual user built by hand (or by a third-party driver) gets.
|
|
81
81
|
*/
|
|
82
82
|
export type ScenarioRpcMap = Record<string, {
|
|
83
83
|
input: any;
|
|
84
84
|
output: any;
|
|
85
85
|
}>;
|
|
86
86
|
/**
|
|
87
|
-
* The
|
|
88
|
-
* known. No declared
|
|
87
|
+
* The virtual user a step's `virtualUser` slot carries, for a project whose virtual users are
|
|
88
|
+
* known. No declared virtual users keeps the open type rather than collapsing to
|
|
89
89
|
* `never` — a project may still build one itself.
|
|
90
90
|
*/
|
|
91
|
-
export type
|
|
91
|
+
export type ScenarioVirtualUserOf<TVirtualUsers> = [
|
|
92
|
+
keyof TVirtualUsers
|
|
93
|
+
] extends [never] ? ScenarioVirtualUser : TVirtualUsers[keyof TVirtualUsers];
|
|
92
94
|
/**
|
|
93
95
|
* A declared person, signed in and acting over the real transport.
|
|
94
96
|
*
|
|
95
|
-
* The runtime half of `
|
|
96
|
-
* flagged `
|
|
97
|
+
* The runtime half of `defineVirtualUsers()`: sign-in materialises a user row
|
|
98
|
+
* flagged `virtualUser: true`, and everything a step or a virtual user does goes
|
|
97
99
|
* through here.
|
|
98
100
|
*/
|
|
99
|
-
export interface
|
|
100
|
-
/** The id it was declared under — what a step's `
|
|
101
|
+
export interface ScenarioVirtualUser<TAgentName extends string = string, TRpcMap extends ScenarioRpcMap = ScenarioRpcMap> {
|
|
102
|
+
/** The id it was declared under — what a step's `virtualUser` slot names. */
|
|
101
103
|
readonly name: string;
|
|
102
104
|
/** Their address, computed from the id. Flows use it for invites/lookups. */
|
|
103
105
|
readonly email: string;
|
|
@@ -110,7 +112,7 @@ export interface ScenarioPersona<TAgentName extends string = string, TRpcMap ext
|
|
|
110
112
|
*/
|
|
111
113
|
invokeRaw<TName extends keyof TRpcMap & string>(rpcName: TName, data: TRpcMap[TName]['input'], options?: ScenarioInvokeOptions): Promise<ScenarioHttpResponse>;
|
|
112
114
|
/** Converse with a Pikku AI agent in character and return its verdict. */
|
|
113
|
-
converse(options: ConverseOptions<TAgentName>): Promise<
|
|
115
|
+
converse(options: ConverseOptions<TAgentName>): Promise<ConversationVerdict>;
|
|
114
116
|
/**
|
|
115
117
|
* The roles this person actually holds on the stage, read back after signing
|
|
116
118
|
* in — what a run compares against the declaration before its first step.
|
|
@@ -123,16 +125,16 @@ export interface ScenarioPersona<TAgentName extends string = string, TRpcMap ext
|
|
|
123
125
|
sessionRoles(): Promise<string[] | null>;
|
|
124
126
|
}
|
|
125
127
|
/**
|
|
126
|
-
* A declared
|
|
128
|
+
* A declared virtual user with its address filled in — what codegen writes and what
|
|
127
129
|
* a seed, a scenario run and a virtual-user run all read.
|
|
128
130
|
*
|
|
129
|
-
* The address is not declared. It is derived from the
|
|
130
|
-
* project's
|
|
131
|
+
* The address is not declared. It is derived from the virtual user's id and the
|
|
132
|
+
* project's virtual user domain, so two people can never accidentally share one and
|
|
131
133
|
* sign in as the same user row — which would silently collapse exactly the
|
|
132
134
|
* isolation scenarios a second person exists for.
|
|
133
135
|
*/
|
|
134
|
-
export type
|
|
136
|
+
export type ResolvedVirtualUser = VirtualUserMeta & {
|
|
135
137
|
email: string;
|
|
136
138
|
};
|
|
137
|
-
/** The injected `
|
|
138
|
-
export type
|
|
139
|
+
/** The injected `virtualUsers` service: virtual user id → virtual user. */
|
|
140
|
+
export type ScenarioVirtualUsers = Record<string, ScenarioVirtualUser>;
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Drain a response into the shape a step can carry: the parsed body (an empty
|
|
3
3
|
* one counting as no body at all) alongside the text it was parsed from.
|
|
4
4
|
*
|
|
5
|
-
* `invokeRaw` returns this, and a step that has to reach past a
|
|
6
|
-
* route with no RPC, an identity no
|
|
5
|
+
* `invokeRaw` returns this, and a step that has to reach past a virtual user — a
|
|
6
|
+
* route with no RPC, an identity no virtual user can hold — reaches for this rather
|
|
7
7
|
* than writing the same record by hand.
|
|
8
8
|
*
|
|
9
9
|
* @example snippet: scenarioPolling
|
|
@@ -28,7 +28,7 @@ const parseJsonBody = (text) => {
|
|
|
28
28
|
/**
|
|
29
29
|
* POST JSON somewhere and report what came back, without throwing on a 4xx/5xx.
|
|
30
30
|
*
|
|
31
|
-
* Every scenario that reaches past a
|
|
31
|
+
* Every scenario that reaches past a virtual user was writing this by hand — the same
|
|
32
32
|
* `content-type`, the same `JSON.stringify`, the same drain — and the copies had
|
|
33
33
|
* drifted: some returned `res.json()`, which loses the status and throws
|
|
34
34
|
* outright when the target answers an empty body or an HTML error page. A
|
|
@@ -25,7 +25,7 @@ import type { SchedulerService } from '../services/scheduler-service.js';
|
|
|
25
25
|
import type { DeploymentService } from '../services/deployment-service.js';
|
|
26
26
|
import type { AgentStorageService } from '../services/agent-storage-service.js';
|
|
27
27
|
import type { ContentService } from '../services/content-service.js';
|
|
28
|
-
import type {
|
|
28
|
+
import type { ScenarioVirtualUserOf, ScenarioVirtualUsers } from '../services/virtual-users-service.js';
|
|
29
29
|
import type { AgentRunnerService } from '../services/agent-runner-service.js';
|
|
30
30
|
import type { AIEmbeddingService } from '../services/ai-embedding-service.js';
|
|
31
31
|
import type { AgentRunStateService } from '../services/agent-run-state-service.js';
|
|
@@ -89,8 +89,8 @@ export interface CoreUserSession {
|
|
|
89
89
|
userId?: string;
|
|
90
90
|
/** The tenant the session is acting inside, when the project has more than one. */
|
|
91
91
|
orgId?: string;
|
|
92
|
-
/** True when the session belongs to a synthetic scenario
|
|
93
|
-
|
|
92
|
+
/** True when the session belongs to a synthetic scenario virtual user — lets audits/analytics address synthetic traffic */
|
|
93
|
+
virtualUser?: boolean;
|
|
94
94
|
/**
|
|
95
95
|
* Scopes granted to this session, checked against a function's `scopes`.
|
|
96
96
|
* Populated by whoever builds the session — core reads them, never fetches.
|
|
@@ -150,7 +150,7 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
|
|
|
150
150
|
*/
|
|
151
151
|
virtualUserRunStore?: VirtualUserRunStore;
|
|
152
152
|
/**
|
|
153
|
-
* Each
|
|
153
|
+
* Each virtual user's cadence, for apps that want their virtual users to keep
|
|
154
154
|
* going without being asked. Separate from the run store on purpose: wiring
|
|
155
155
|
* nothing is how an app says it only wants the runs it starts itself.
|
|
156
156
|
*/
|
|
@@ -214,7 +214,7 @@ export type GetCredential<TCredentials = Record<string, unknown>> = string exten
|
|
|
214
214
|
<K extends keyof TCredentials & string>(name: K): TCredentials[K] | null | Promise<TCredentials[K] | null>;
|
|
215
215
|
<T = unknown>(name: string): T | null | Promise<T | null>;
|
|
216
216
|
};
|
|
217
|
-
export type PikkuWire<In = unknown, Out = unknown, HasInitialSession extends boolean = false, UserSession extends CoreUserSession = CoreUserSession, TypedRPC extends PikkuRPC = PikkuRPC, IsChannel extends true | null = null, MCPTools extends string | never = never, TypedWorkflow extends PikkuWorkflowWire | never = PikkuWorkflowWire, TriggerOutput = unknown, TypedScenario extends PikkuScenarioWire<any> | never = PikkuScenarioWire<any>,
|
|
217
|
+
export type PikkuWire<In = unknown, Out = unknown, HasInitialSession extends boolean = false, UserSession extends CoreUserSession = CoreUserSession, TypedRPC extends PikkuRPC = PikkuRPC, IsChannel extends true | null = null, MCPTools extends string | never = never, TypedWorkflow extends PikkuWorkflowWire | never = PikkuWorkflowWire, TriggerOutput = unknown, TypedScenario extends PikkuScenarioWire<any> | never = PikkuScenarioWire<any>, TScenarioVirtualUsers extends ScenarioVirtualUsers = ScenarioVirtualUsers, TypedCredentials = Record<string, unknown>> = {
|
|
218
218
|
/** Always present — lazily initialised on first access for every function invocation */
|
|
219
219
|
rpc: TypedRPC;
|
|
220
220
|
/** Get a single credential by name — lazy-loads from CredentialService on first call, sync thereafter */
|
|
@@ -241,17 +241,17 @@ export type PikkuWire<In = unknown, Out = unknown, HasInitialSession extends boo
|
|
|
241
241
|
cli: PikkuCLI<TypedRPC['remote']>;
|
|
242
242
|
workflow: TypedWorkflow;
|
|
243
243
|
scenario: TypedScenario;
|
|
244
|
-
|
|
244
|
+
virtualUsers: TScenarioVirtualUsers;
|
|
245
245
|
/**
|
|
246
|
-
* The
|
|
246
|
+
* The virtual user this step runs as, injected by the runner.
|
|
247
247
|
*
|
|
248
248
|
* Required on a `browser` binding, which cannot be provisioned without one,
|
|
249
|
-
* and on any binding whose step declares `
|
|
250
|
-
* neither has no `
|
|
249
|
+
* and on any binding whose step declares `virtualUser: true`. A step that declares
|
|
250
|
+
* neither has no `virtualUser` on its wire at all rather than an optional one —
|
|
251
251
|
* a pure assertion has nobody to be, and `attemptsSignIn` deliberately posts
|
|
252
|
-
* credentials instead of reusing
|
|
252
|
+
* credentials instead of reusing a virtual user's session.
|
|
253
253
|
*/
|
|
254
|
-
|
|
254
|
+
virtualUser: ScenarioVirtualUserOf<TScenarioVirtualUsers>;
|
|
255
255
|
/** Present on every scenario step invocation */
|
|
256
256
|
scenarioStep: PikkuScenarioStepWire;
|
|
257
257
|
/** Present only when the runner provisioned a browser for this step */
|
|
@@ -3,7 +3,7 @@ import type { CoreSystemRoles } from './role.types.js';
|
|
|
3
3
|
* No-op function for declaring system roles.
|
|
4
4
|
* This exists purely for TypeScript type checking and will be tree-shaken.
|
|
5
5
|
* The CLI extracts metadata via AST parsing and generates a `SystemRoleName`
|
|
6
|
-
* union, so a
|
|
6
|
+
* union, so a virtual user naming an undeclared role fails the build.
|
|
7
7
|
*
|
|
8
8
|
* A system role is a role that ships with the product: the console may grant
|
|
9
9
|
* it, but cannot rename, re-scope or delete it. Roles an admin composes in the
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* No-op function for declaring system roles.
|
|
3
3
|
* This exists purely for TypeScript type checking and will be tree-shaken.
|
|
4
4
|
* The CLI extracts metadata via AST parsing and generates a `SystemRoleName`
|
|
5
|
-
* union, so a
|
|
5
|
+
* union, so a virtual user naming an undeclared role fails the build.
|
|
6
6
|
*
|
|
7
7
|
* A system role is a role that ships with the product: the console may grant
|
|
8
8
|
* it, but cannot rename, re-scope or delete it. Roles an admin composes in the
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
export type
|
|
1
|
+
export type ConversationApprovalPolicy = 'in-virtual-user' | 'always' | 'never';
|
|
2
2
|
export interface ConverseOptions<TAgentName extends string = string> {
|
|
3
3
|
agent: TAgentName;
|
|
4
|
-
/** What the
|
|
4
|
+
/** What the virtual user is trying to get the agent to accomplish. */
|
|
5
5
|
task: string;
|
|
6
|
-
/** Natural-language success criterion the
|
|
6
|
+
/** Natural-language success criterion the virtual user judges the conversation against. */
|
|
7
7
|
evaluate: string;
|
|
8
|
-
approvals?:
|
|
8
|
+
approvals?: ConversationApprovalPolicy;
|
|
9
9
|
model?: string;
|
|
10
10
|
maxTurns?: number;
|
|
11
11
|
}
|
|
12
|
-
export interface
|
|
12
|
+
export interface ConversationVerdict {
|
|
13
13
|
passed: boolean;
|
|
14
14
|
reasoning: string;
|
|
15
15
|
transcript: string[];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ConversationApprovalPolicy, ConversationVerdict, TargetAgentDriver } from './conversation.types.js';
|
|
2
|
+
import type { ResolvedVirtualUser } from '../../../services/virtual-users-service.js';
|
|
3
|
+
import type { AgentRunnerParams, AgentStepResult } from '../../../services/agent-runner-service.js';
|
|
4
|
+
/** The LLM call the virtual user uses for its own turns/decisions/evaluation. */
|
|
5
|
+
export type VirtualUserLLM = (params: AgentRunnerParams) => Promise<AgentStepResult>;
|
|
6
|
+
export interface RunConversationParams {
|
|
7
|
+
/** The person being played — name, job title, personality. */
|
|
8
|
+
virtualUser: ResolvedVirtualUser;
|
|
9
|
+
/** The id they were declared under (for transcript labelling). */
|
|
10
|
+
virtualUserId: string;
|
|
11
|
+
/** What the virtual user is trying to get the target agent to accomplish. */
|
|
12
|
+
task: string;
|
|
13
|
+
/** Natural-language success criterion the virtual user evaluates at the end. */
|
|
14
|
+
evaluate: string;
|
|
15
|
+
/** How the virtual user answers the target agent's tool-approval requests. */
|
|
16
|
+
approvals?: ConversationApprovalPolicy;
|
|
17
|
+
/** Model the virtual user uses for its own turns/decisions. */
|
|
18
|
+
model: string;
|
|
19
|
+
/** Hard cap on conversation turns. Default 12. */
|
|
20
|
+
maxTurns?: number;
|
|
21
|
+
/** Hard cap on tool-approval rounds within a single target turn. Default 16. */
|
|
22
|
+
maxApprovalRounds?: number;
|
|
23
|
+
/** Transport that drives the target agent (HTTP in production). */
|
|
24
|
+
target: TargetAgentDriver;
|
|
25
|
+
/** The virtual user's own LLM. */
|
|
26
|
+
llm: VirtualUserLLM;
|
|
27
|
+
/** Display name of the target agent (transcript labelling). */
|
|
28
|
+
agentName: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Run a conversation: an LLM-driven virtual user holds a real multi-turn exchange
|
|
32
|
+
* with a target agent (driven via the injected transport), answers the target's
|
|
33
|
+
* tool-approval requests in-virtual-user, then evaluates whether the task was met.
|
|
34
|
+
* Deterministic checks are the caller's responsibility.
|
|
35
|
+
*/
|
|
36
|
+
export declare function runConversation(params: RunConversationParams): Promise<ConversationVerdict>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/** One turn the
|
|
2
|
-
const
|
|
1
|
+
/** One turn the virtual user takes: the message to send and whether it's finished. */
|
|
2
|
+
const VIRTUAL_USER_TURN_SCHEMA = {
|
|
3
3
|
type: 'object',
|
|
4
4
|
properties: {
|
|
5
5
|
message: { type: 'string' },
|
|
@@ -7,7 +7,7 @@ const ACTOR_TURN_SCHEMA = {
|
|
|
7
7
|
},
|
|
8
8
|
required: ['message', 'done'],
|
|
9
9
|
};
|
|
10
|
-
/** The
|
|
10
|
+
/** The virtual user's approve/deny decision for each pending tool request. */
|
|
11
11
|
const APPROVAL_DECISION_SCHEMA = {
|
|
12
12
|
type: 'object',
|
|
13
13
|
properties: {
|
|
@@ -26,7 +26,7 @@ const APPROVAL_DECISION_SCHEMA = {
|
|
|
26
26
|
},
|
|
27
27
|
required: ['decisions'],
|
|
28
28
|
};
|
|
29
|
-
/** The
|
|
29
|
+
/** The virtual user's final verdict on whether the task was accomplished. */
|
|
30
30
|
const EVALUATION_SCHEMA = {
|
|
31
31
|
type: 'object',
|
|
32
32
|
properties: {
|
|
@@ -60,25 +60,25 @@ function readObject(result) {
|
|
|
60
60
|
}
|
|
61
61
|
return null;
|
|
62
62
|
}
|
|
63
|
-
function
|
|
63
|
+
function virtualUserConversationInstructions(virtualUser, task) {
|
|
64
64
|
return [
|
|
65
65
|
`You are role-playing a real user interacting with an AI assistant. Stay in character at all times — you are the user, not the assistant.`,
|
|
66
|
-
`Your name is ${
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
? `Your personality and communication style: ${
|
|
66
|
+
`Your name is ${virtualUser.name}.`,
|
|
67
|
+
virtualUser.jobTitle ? `Your role: ${virtualUser.jobTitle}.` : '',
|
|
68
|
+
virtualUser.personality
|
|
69
|
+
? `Your personality and communication style: ${virtualUser.personality}. Match this tone, vocabulary, and level of detail exactly.`
|
|
70
70
|
: '',
|
|
71
71
|
`Your goal in this conversation: ${task}.`,
|
|
72
72
|
`Send one message at a time. Set "done" to true only once your goal is clearly accomplished, or clearly impossible.`,
|
|
73
|
-
// knowledge: decisions/internals/the-
|
|
73
|
+
// knowledge: decisions/internals/the-virtual-user-prompt-says-json-because-of-json-object-mode.md
|
|
74
74
|
`Reply with json matching the schema you are given, and nothing else.`,
|
|
75
75
|
]
|
|
76
76
|
.filter(Boolean)
|
|
77
77
|
.join('\n');
|
|
78
78
|
}
|
|
79
|
-
/** Route the target agent's pending tool approvals through the
|
|
79
|
+
/** Route the target agent's pending tool approvals through the virtual user. */
|
|
80
80
|
async function decideApprovals(params, instructions, pending) {
|
|
81
|
-
const policy = params.approvals ?? 'in-
|
|
81
|
+
const policy = params.approvals ?? 'in-virtual-user';
|
|
82
82
|
if (policy === 'always') {
|
|
83
83
|
return pending.map((p) => ({ toolCallId: p.toolCallId, approved: true }));
|
|
84
84
|
}
|
|
@@ -90,7 +90,7 @@ async function decideApprovals(params, instructions, pending) {
|
|
|
90
90
|
.join('\n');
|
|
91
91
|
const result = await params.llm({
|
|
92
92
|
model: params.model,
|
|
93
|
-
instructions: `${instructions}\nThe assistant is asking permission to run tools on your behalf. Decide whether YOU, as this
|
|
93
|
+
instructions: `${instructions}\nThe assistant is asking permission to run tools on your behalf. Decide whether YOU, as this virtual user, would allow each one.`,
|
|
94
94
|
messages: [
|
|
95
95
|
msg('user', `The assistant wants to run these tools:\n${summary}\nApprove or deny each toolCallId.`),
|
|
96
96
|
],
|
|
@@ -106,7 +106,7 @@ async function decideApprovals(params, instructions, pending) {
|
|
|
106
106
|
return { toolCallId: p.toolCallId, approved: match?.approved ?? false };
|
|
107
107
|
});
|
|
108
108
|
}
|
|
109
|
-
/** Drive the target to a non-suspended reply, routing approvals to the
|
|
109
|
+
/** Drive the target to a non-suspended reply, routing approvals to the virtual user. */
|
|
110
110
|
async function converseWithTarget(params, instructions, message) {
|
|
111
111
|
const maxRounds = params.maxApprovalRounds ?? DEFAULT_MAX_APPROVAL_ROUNDS;
|
|
112
112
|
let reply = await params.target.run(message);
|
|
@@ -124,40 +124,40 @@ async function converseWithTarget(params, instructions, message) {
|
|
|
124
124
|
return reply;
|
|
125
125
|
}
|
|
126
126
|
/**
|
|
127
|
-
* Run a conversation: an LLM-driven
|
|
127
|
+
* Run a conversation: an LLM-driven virtual user holds a real multi-turn exchange
|
|
128
128
|
* with a target agent (driven via the injected transport), answers the target's
|
|
129
|
-
* tool-approval requests in-
|
|
129
|
+
* tool-approval requests in-virtual-user, then evaluates whether the task was met.
|
|
130
130
|
* Deterministic checks are the caller's responsibility.
|
|
131
131
|
*/
|
|
132
132
|
export async function runConversation(params) {
|
|
133
133
|
const maxTurns = params.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
134
|
-
const instructions =
|
|
135
|
-
// Seed a kickoff so the very first
|
|
136
|
-
// (providers reject an empty prompt). It's an instruction TO the
|
|
134
|
+
const instructions = virtualUserConversationInstructions(params.virtualUser, params.task);
|
|
135
|
+
// Seed a kickoff so the very first virtual user turn has a non-empty message list
|
|
136
|
+
// (providers reject an empty prompt). It's an instruction TO the virtual user, so
|
|
137
137
|
// it never appears in the transcript.
|
|
138
|
-
const
|
|
138
|
+
const virtualUserMessages = [
|
|
139
139
|
msg('user', 'Begin the conversation now — send your first message to the assistant to work towards your goal.'),
|
|
140
140
|
];
|
|
141
141
|
const transcript = [];
|
|
142
142
|
for (let turn = 0; turn < maxTurns; turn++) {
|
|
143
|
-
const
|
|
143
|
+
const virtualUserResult = await params.llm({
|
|
144
144
|
model: params.model,
|
|
145
145
|
instructions,
|
|
146
|
-
messages:
|
|
146
|
+
messages: virtualUserMessages,
|
|
147
147
|
tools: [],
|
|
148
148
|
maxSteps: 1,
|
|
149
149
|
toolChoice: 'none',
|
|
150
|
-
outputSchema:
|
|
150
|
+
outputSchema: VIRTUAL_USER_TURN_SCHEMA,
|
|
151
151
|
});
|
|
152
|
-
const turnData = readObject(
|
|
153
|
-
const
|
|
154
|
-
if (!
|
|
152
|
+
const turnData = readObject(virtualUserResult);
|
|
153
|
+
const virtualUserMessage = turnData?.message?.trim();
|
|
154
|
+
if (!virtualUserMessage) {
|
|
155
155
|
break;
|
|
156
156
|
}
|
|
157
|
-
|
|
158
|
-
transcript.push(`${params.
|
|
159
|
-
const reply = await converseWithTarget(params, instructions,
|
|
160
|
-
|
|
157
|
+
virtualUserMessages.push(msg('assistant', virtualUserMessage));
|
|
158
|
+
transcript.push(`${params.virtualUser.name}: ${virtualUserMessage}`);
|
|
159
|
+
const reply = await converseWithTarget(params, instructions, virtualUserMessage);
|
|
160
|
+
virtualUserMessages.push(msg('user', reply.text ?? ''));
|
|
161
161
|
transcript.push(`${params.agentName}: ${reply.text ?? ''}`);
|
|
162
162
|
if (turnData?.done) {
|
|
163
163
|
break;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { CoreVirtualUsers } from './virtual-user-declaration.types.js';
|
|
2
|
+
/**
|
|
3
|
+
* No-op function for declaring virtual users.
|
|
4
|
+
* This exists purely for TypeScript type checking and will be tree-shaken.
|
|
5
|
+
* The CLI extracts metadata via AST parsing and generates a `VirtualUserId` union
|
|
6
|
+
* and a `virtual-users.gen.json` the console and `pikku virtual-user run` read off disk —
|
|
7
|
+
* describing, listing or running one never has to load the app.
|
|
8
|
+
*
|
|
9
|
+
* Declaring is not running: a virtual user that exists only to be acted upon
|
|
10
|
+
* (the account an admin bans, the colleague a document is shared with) is
|
|
11
|
+
* seeded and never run.
|
|
12
|
+
*
|
|
13
|
+
* Exactly one `defineVirtualUsers(...)` is allowed per codebase, so there is one
|
|
14
|
+
* place to read the cast from and one place to add to. A second call — even in
|
|
15
|
+
* the same file — fails the build.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* defineVirtualUsers({
|
|
20
|
+
* susan: {
|
|
21
|
+
* name: 'Susan',
|
|
22
|
+
* jobTitle: 'Buys for a small café',
|
|
23
|
+
* roles: ['buyer'],
|
|
24
|
+
* personality: 'Hunts cheap deals. Tries three coupon codes before giving up.',
|
|
25
|
+
* goals: ['Get the weekly order in under five minutes'],
|
|
26
|
+
* disposition: 'careless',
|
|
27
|
+
* account: {},
|
|
28
|
+
* },
|
|
29
|
+
* })
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare const defineVirtualUsers: (_config: CoreVirtualUsers) => void;
|