@guidify-ai/vapi-studio 0.1.0
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/LICENSE +21 -0
- package/README.md +129 -0
- package/agent/AGENTS.md +42 -0
- package/agent/CLAUDE.md +15 -0
- package/agent/claude/rules/ui-and-api-identity.md +9 -0
- package/agent/claude/rules/vapi-studio-best-practices.md +36 -0
- package/agent/cursor/ui-and-api-identity.mdc +16 -0
- package/agent/cursor/vapi-studio-best-practices.mdc +43 -0
- package/dist/adapters/vapi/vapi-sse.compiler.d.ts +61 -0
- package/dist/adapters/vapi/vapi-sse.compiler.d.ts.map +1 -0
- package/dist/adapters/vapi/vapi-sse.compiler.js +396 -0
- package/dist/adapters/vapi/vapi-sse.compiler.js.map +1 -0
- package/dist/analytics/analytics-tags.d.ts +60 -0
- package/dist/analytics/analytics-tags.d.ts.map +1 -0
- package/dist/analytics/analytics-tags.js +27 -0
- package/dist/analytics/analytics-tags.js.map +1 -0
- package/dist/brain/adapters/chatgpt-brain.adapter.d.ts +27 -0
- package/dist/brain/adapters/chatgpt-brain.adapter.d.ts.map +1 -0
- package/dist/brain/adapters/chatgpt-brain.adapter.js +94 -0
- package/dist/brain/adapters/chatgpt-brain.adapter.js.map +1 -0
- package/dist/brain/adapters/claude-brain.adapter.d.ts +24 -0
- package/dist/brain/adapters/claude-brain.adapter.d.ts.map +1 -0
- package/dist/brain/adapters/claude-brain.adapter.js +96 -0
- package/dist/brain/adapters/claude-brain.adapter.js.map +1 -0
- package/dist/brain/adapters/gemini-brain.adapter.d.ts +24 -0
- package/dist/brain/adapters/gemini-brain.adapter.d.ts.map +1 -0
- package/dist/brain/adapters/gemini-brain.adapter.js +106 -0
- package/dist/brain/adapters/gemini-brain.adapter.js.map +1 -0
- package/dist/brain/adapters/grok-brain.adapter.d.ts +24 -0
- package/dist/brain/adapters/grok-brain.adapter.d.ts.map +1 -0
- package/dist/brain/adapters/grok-brain.adapter.js +90 -0
- package/dist/brain/adapters/grok-brain.adapter.js.map +1 -0
- package/dist/brain/adapters/json-llm-brain.base.d.ts +63 -0
- package/dist/brain/adapters/json-llm-brain.base.d.ts.map +1 -0
- package/dist/brain/adapters/json-llm-brain.base.js +511 -0
- package/dist/brain/adapters/json-llm-brain.base.js.map +1 -0
- package/dist/brain/adapters/mock-brain.adapter.d.ts +32 -0
- package/dist/brain/adapters/mock-brain.adapter.d.ts.map +1 -0
- package/dist/brain/adapters/mock-brain.adapter.js +201 -0
- package/dist/brain/adapters/mock-brain.adapter.js.map +1 -0
- package/dist/brain/adapters/mock-provider-brains.adapter.d.ts +22 -0
- package/dist/brain/adapters/mock-provider-brains.adapter.d.ts.map +1 -0
- package/dist/brain/adapters/mock-provider-brains.adapter.js +48 -0
- package/dist/brain/adapters/mock-provider-brains.adapter.js.map +1 -0
- package/dist/brain/brain-clarify.d.ts +75 -0
- package/dist/brain/brain-clarify.d.ts.map +1 -0
- package/dist/brain/brain-clarify.js +146 -0
- package/dist/brain/brain-clarify.js.map +1 -0
- package/dist/brain/brain-config.d.ts +20 -0
- package/dist/brain/brain-config.d.ts.map +1 -0
- package/dist/brain/brain-config.js +20 -0
- package/dist/brain/brain-config.js.map +1 -0
- package/dist/brain/brain-judge.d.ts +68 -0
- package/dist/brain/brain-judge.d.ts.map +1 -0
- package/dist/brain/brain-judge.js +111 -0
- package/dist/brain/brain-judge.js.map +1 -0
- package/dist/brain/brain-ranking.d.ts +41 -0
- package/dist/brain/brain-ranking.d.ts.map +1 -0
- package/dist/brain/brain-ranking.js +103 -0
- package/dist/brain/brain-ranking.js.map +1 -0
- package/dist/brain/brain-usage.tracker.d.ts +48 -0
- package/dist/brain/brain-usage.tracker.d.ts.map +1 -0
- package/dist/brain/brain-usage.tracker.js +106 -0
- package/dist/brain/brain-usage.tracker.js.map +1 -0
- package/dist/brain/brain.port.d.ts +63 -0
- package/dist/brain/brain.port.d.ts.map +1 -0
- package/dist/brain/brain.port.js +7 -0
- package/dist/brain/brain.port.js.map +1 -0
- package/dist/brain/brain.service.d.ts +14 -0
- package/dist/brain/brain.service.d.ts.map +1 -0
- package/dist/brain/brain.service.js +27 -0
- package/dist/brain/brain.service.js.map +1 -0
- package/dist/brain/claude-cheap-models.d.ts +18 -0
- package/dist/brain/claude-cheap-models.d.ts.map +1 -0
- package/dist/brain/claude-cheap-models.js +53 -0
- package/dist/brain/claude-cheap-models.js.map +1 -0
- package/dist/brain/gemini-cheap-models.d.ts +18 -0
- package/dist/brain/gemini-cheap-models.d.ts.map +1 -0
- package/dist/brain/gemini-cheap-models.js +53 -0
- package/dist/brain/gemini-cheap-models.js.map +1 -0
- package/dist/brain/grok-cheap-models.d.ts +18 -0
- package/dist/brain/grok-cheap-models.d.ts.map +1 -0
- package/dist/brain/grok-cheap-models.js +53 -0
- package/dist/brain/grok-cheap-models.js.map +1 -0
- package/dist/brain/llm-model-pricing.d.ts +29 -0
- package/dist/brain/llm-model-pricing.d.ts.map +1 -0
- package/dist/brain/llm-model-pricing.js +38 -0
- package/dist/brain/llm-model-pricing.js.map +1 -0
- package/dist/brain/openai-cheap-models.d.ts +34 -0
- package/dist/brain/openai-cheap-models.d.ts.map +1 -0
- package/dist/brain/openai-cheap-models.js +72 -0
- package/dist/brain/openai-cheap-models.js.map +1 -0
- package/dist/brain/prompt-injection-guard.d.ts +34 -0
- package/dist/brain/prompt-injection-guard.d.ts.map +1 -0
- package/dist/brain/prompt-injection-guard.js +109 -0
- package/dist/brain/prompt-injection-guard.js.map +1 -0
- package/dist/channel/channel-tools.d.ts +72 -0
- package/dist/channel/channel-tools.d.ts.map +1 -0
- package/dist/channel/channel-tools.js +131 -0
- package/dist/channel/channel-tools.js.map +1 -0
- package/dist/conversation/call-turn-queue.d.ts +64 -0
- package/dist/conversation/call-turn-queue.d.ts.map +1 -0
- package/dist/conversation/call-turn-queue.js +156 -0
- package/dist/conversation/call-turn-queue.js.map +1 -0
- package/dist/conversation/conversation-bootstrap.service.d.ts +60 -0
- package/dist/conversation/conversation-bootstrap.service.d.ts.map +1 -0
- package/dist/conversation/conversation-bootstrap.service.js +383 -0
- package/dist/conversation/conversation-bootstrap.service.js.map +1 -0
- package/dist/conversation/conversation-entry.d.ts +38 -0
- package/dist/conversation/conversation-entry.d.ts.map +1 -0
- package/dist/conversation/conversation-entry.js +17 -0
- package/dist/conversation/conversation-entry.js.map +1 -0
- package/dist/conversation/conversation-history.d.ts +31 -0
- package/dist/conversation/conversation-history.d.ts.map +1 -0
- package/dist/conversation/conversation-history.js +53 -0
- package/dist/conversation/conversation-history.js.map +1 -0
- package/dist/conversation/conversation-limits.d.ts +49 -0
- package/dist/conversation/conversation-limits.d.ts.map +1 -0
- package/dist/conversation/conversation-limits.js +44 -0
- package/dist/conversation/conversation-limits.js.map +1 -0
- package/dist/conversation/conversation-resume.service.d.ts +39 -0
- package/dist/conversation/conversation-resume.service.d.ts.map +1 -0
- package/dist/conversation/conversation-resume.service.js +234 -0
- package/dist/conversation/conversation-resume.service.js.map +1 -0
- package/dist/conversation/conversation-schema.d.ts +23 -0
- package/dist/conversation/conversation-schema.d.ts.map +1 -0
- package/dist/conversation/conversation-schema.js +3 -0
- package/dist/conversation/conversation-schema.js.map +1 -0
- package/dist/conversation/listen-expectation.d.ts +145 -0
- package/dist/conversation/listen-expectation.d.ts.map +1 -0
- package/dist/conversation/listen-expectation.js +219 -0
- package/dist/conversation/listen-expectation.js.map +1 -0
- package/dist/conversation/listen-timeout.d.ts +42 -0
- package/dist/conversation/listen-timeout.d.ts.map +1 -0
- package/dist/conversation/listen-timeout.js +57 -0
- package/dist/conversation/listen-timeout.js.map +1 -0
- package/dist/conversation/supervised-conversation.d.ts +64 -0
- package/dist/conversation/supervised-conversation.d.ts.map +1 -0
- package/dist/conversation/supervised-conversation.js +198 -0
- package/dist/conversation/supervised-conversation.js.map +1 -0
- package/dist/conversation/supervised-conversation.registry.d.ts +14 -0
- package/dist/conversation/supervised-conversation.registry.d.ts.map +1 -0
- package/dist/conversation/supervised-conversation.registry.js +47 -0
- package/dist/conversation/supervised-conversation.registry.js.map +1 -0
- package/dist/conversation/types.d.ts +52 -0
- package/dist/conversation/types.d.ts.map +1 -0
- package/dist/conversation/types.js +3 -0
- package/dist/conversation/types.js.map +1 -0
- package/dist/events/conversation-console.d.ts +17 -0
- package/dist/events/conversation-console.d.ts.map +1 -0
- package/dist/events/conversation-console.js +863 -0
- package/dist/events/conversation-console.js.map +1 -0
- package/dist/events/daily-log.driver.d.ts +35 -0
- package/dist/events/daily-log.driver.d.ts.map +1 -0
- package/dist/events/daily-log.driver.js +206 -0
- package/dist/events/daily-log.driver.js.map +1 -0
- package/dist/events/event.service.d.ts +24 -0
- package/dist/events/event.service.d.ts.map +1 -0
- package/dist/events/event.service.js +141 -0
- package/dist/events/event.service.js.map +1 -0
- package/dist/events/route-forensics.d.ts +38 -0
- package/dist/events/route-forensics.d.ts.map +1 -0
- package/dist/events/route-forensics.js +85 -0
- package/dist/events/route-forensics.js.map +1 -0
- package/dist/events/studio-event-bus.d.ts +17 -0
- package/dist/events/studio-event-bus.d.ts.map +1 -0
- package/dist/events/studio-event-bus.js +37 -0
- package/dist/events/studio-event-bus.js.map +1 -0
- package/dist/events/studio-event.d.ts +44 -0
- package/dist/events/studio-event.d.ts.map +1 -0
- package/dist/events/studio-event.js +27 -0
- package/dist/events/studio-event.js.map +1 -0
- package/dist/flow/condition.d.ts +35 -0
- package/dist/flow/condition.d.ts.map +1 -0
- package/dist/flow/condition.js +246 -0
- package/dist/flow/condition.js.map +1 -0
- package/dist/flow/flow-loader.d.ts +71 -0
- package/dist/flow/flow-loader.d.ts.map +1 -0
- package/dist/flow/flow-loader.js +182 -0
- package/dist/flow/flow-loader.js.map +1 -0
- package/dist/forms/form-html.d.ts +30 -0
- package/dist/forms/form-html.d.ts.map +1 -0
- package/dist/forms/form-html.js +130 -0
- package/dist/forms/form-html.js.map +1 -0
- package/dist/forms/form.tokens.d.ts +3 -0
- package/dist/forms/form.tokens.d.ts.map +1 -0
- package/dist/forms/form.tokens.js +6 -0
- package/dist/forms/form.tokens.js.map +1 -0
- package/dist/forms/form.types.d.ts +81 -0
- package/dist/forms/form.types.d.ts.map +1 -0
- package/dist/forms/form.types.js +36 -0
- package/dist/forms/form.types.js.map +1 -0
- package/dist/forms/forms.service.d.ts +52 -0
- package/dist/forms/forms.service.d.ts.map +1 -0
- package/dist/forms/forms.service.js +434 -0
- package/dist/forms/forms.service.js.map +1 -0
- package/dist/forms/noop-form-dispose.adapter.d.ts +12 -0
- package/dist/forms/noop-form-dispose.adapter.d.ts.map +1 -0
- package/dist/forms/noop-form-dispose.adapter.js +27 -0
- package/dist/forms/noop-form-dispose.adapter.js.map +1 -0
- package/dist/forms/twilio-sms-form-dispose.adapter.d.ts +33 -0
- package/dist/forms/twilio-sms-form-dispose.adapter.d.ts.map +1 -0
- package/dist/forms/twilio-sms-form-dispose.adapter.js +125 -0
- package/dist/forms/twilio-sms-form-dispose.adapter.js.map +1 -0
- package/dist/forms/twilio-sms.env.d.ts +62 -0
- package/dist/forms/twilio-sms.env.d.ts.map +1 -0
- package/dist/forms/twilio-sms.env.js +147 -0
- package/dist/forms/twilio-sms.env.js.map +1 -0
- package/dist/forms/twilio-sms.sender.d.ts +27 -0
- package/dist/forms/twilio-sms.sender.d.ts.map +1 -0
- package/dist/forms/twilio-sms.sender.js +75 -0
- package/dist/forms/twilio-sms.sender.js.map +1 -0
- package/dist/identity/email-parse.d.ts +20 -0
- package/dist/identity/email-parse.d.ts.map +1 -0
- package/dist/identity/email-parse.js +102 -0
- package/dist/identity/email-parse.js.map +1 -0
- package/dist/identity/index.d.ts +17 -0
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/index.js +23 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/index.d.ts +108 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +300 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/integration-client.d.ts +41 -0
- package/dist/integrations/integration-client.d.ts.map +1 -0
- package/dist/integrations/integration-client.js +152 -0
- package/dist/integrations/integration-client.js.map +1 -0
- package/dist/integrations/jwt-hmac.d.ts +33 -0
- package/dist/integrations/jwt-hmac.d.ts.map +1 -0
- package/dist/integrations/jwt-hmac.js +108 -0
- package/dist/integrations/jwt-hmac.js.map +1 -0
- package/dist/intention/code-intention.d.ts +94 -0
- package/dist/intention/code-intention.d.ts.map +1 -0
- package/dist/intention/code-intention.js +89 -0
- package/dist/intention/code-intention.js.map +1 -0
- package/dist/intention/intention-constants.d.ts +37 -0
- package/dist/intention/intention-constants.d.ts.map +1 -0
- package/dist/intention/intention-constants.js +37 -0
- package/dist/intention/intention-constants.js.map +1 -0
- package/dist/intentions/standard-intentions.d.ts +26 -0
- package/dist/intentions/standard-intentions.d.ts.map +1 -0
- package/dist/intentions/standard-intentions.js +28 -0
- package/dist/intentions/standard-intentions.js.map +1 -0
- package/dist/node/agent-node.d.ts +156 -0
- package/dist/node/agent-node.d.ts.map +1 -0
- package/dist/node/agent-node.js +179 -0
- package/dist/node/agent-node.js.map +1 -0
- package/dist/node/catch-directive.d.ts +33 -0
- package/dist/node/catch-directive.d.ts.map +1 -0
- package/dist/node/catch-directive.js +41 -0
- package/dist/node/catch-directive.js.map +1 -0
- package/dist/output/conversation-output.d.ts +113 -0
- package/dist/output/conversation-output.d.ts.map +1 -0
- package/dist/output/conversation-output.js +100 -0
- package/dist/output/conversation-output.js.map +1 -0
- package/dist/persistence/conversation.entity.d.ts +27 -0
- package/dist/persistence/conversation.entity.d.ts.map +1 -0
- package/dist/persistence/conversation.entity.js +115 -0
- package/dist/persistence/conversation.entity.js.map +1 -0
- package/dist/persistence/conversation.repository.d.ts +82 -0
- package/dist/persistence/conversation.repository.d.ts.map +1 -0
- package/dist/persistence/conversation.repository.js +277 -0
- package/dist/persistence/conversation.repository.js.map +1 -0
- package/dist/persistence/project.entity.d.ts +12 -0
- package/dist/persistence/project.entity.d.ts.map +1 -0
- package/dist/persistence/project.entity.js +46 -0
- package/dist/persistence/project.entity.js.map +1 -0
- package/dist/persistence/project.repository.d.ts +17 -0
- package/dist/persistence/project.repository.d.ts.map +1 -0
- package/dist/persistence/project.repository.js +69 -0
- package/dist/persistence/project.repository.js.map +1 -0
- package/dist/persistence/provider-ingress.entity.d.ts +18 -0
- package/dist/persistence/provider-ingress.entity.d.ts.map +1 -0
- package/dist/persistence/provider-ingress.entity.js +91 -0
- package/dist/persistence/provider-ingress.entity.js.map +1 -0
- package/dist/persistence/provider-ingress.repository.d.ts +21 -0
- package/dist/persistence/provider-ingress.repository.d.ts.map +1 -0
- package/dist/persistence/provider-ingress.repository.js +76 -0
- package/dist/persistence/provider-ingress.repository.js.map +1 -0
- package/dist/studio-ui/assets/index-BGd2OhLv.js +84 -0
- package/dist/studio-ui/assets/index-BGd2OhLv.js.map +1 -0
- package/dist/studio-ui/assets/index-DnJos5x2.css +1 -0
- package/dist/studio-ui/index.html +13 -0
- package/dist/studio-ui-mount/index.d.ts +6 -0
- package/dist/studio-ui-mount/index.d.ts.map +1 -0
- package/dist/studio-ui-mount/index.js +13 -0
- package/dist/studio-ui-mount/index.js.map +1 -0
- package/dist/studio-ui-mount/resolve-studio-ui-root.d.ts +6 -0
- package/dist/studio-ui-mount/resolve-studio-ui-root.d.ts.map +1 -0
- package/dist/studio-ui-mount/resolve-studio-ui-root.js +25 -0
- package/dist/studio-ui-mount/resolve-studio-ui-root.js.map +1 -0
- package/dist/studio-ui-mount/studio-spa.controller.d.ts +18 -0
- package/dist/studio-ui-mount/studio-spa.controller.d.ts.map +1 -0
- package/dist/studio-ui-mount/studio-spa.controller.js +57 -0
- package/dist/studio-ui-mount/studio-spa.controller.js.map +1 -0
- package/dist/studio-ui-mount/studio-ui.module.d.ts +15 -0
- package/dist/studio-ui-mount/studio-ui.module.d.ts.map +1 -0
- package/dist/studio-ui-mount/studio-ui.module.js +47 -0
- package/dist/studio-ui-mount/studio-ui.module.js.map +1 -0
- package/dist/studio-ui-mount/studio-ui.tokens.d.ts +2 -0
- package/dist/studio-ui-mount/studio-ui.tokens.d.ts.map +1 -0
- package/dist/studio-ui-mount/studio-ui.tokens.js +5 -0
- package/dist/studio-ui-mount/studio-ui.tokens.js.map +1 -0
- package/dist/supervisor/supervisor-cascade.d.ts +55 -0
- package/dist/supervisor/supervisor-cascade.d.ts.map +1 -0
- package/dist/supervisor/supervisor-cascade.js +261 -0
- package/dist/supervisor/supervisor-cascade.js.map +1 -0
- package/dist/supervisor/supervisor-engine.d.ts +9 -0
- package/dist/supervisor/supervisor-engine.d.ts.map +1 -0
- package/dist/supervisor/supervisor-engine.js +64 -0
- package/dist/supervisor/supervisor-engine.js.map +1 -0
- package/dist/supervisor/supervisor-limits.d.ts +8 -0
- package/dist/supervisor/supervisor-limits.d.ts.map +1 -0
- package/dist/supervisor/supervisor-limits.js +36 -0
- package/dist/supervisor/supervisor-limits.js.map +1 -0
- package/dist/supervisor/supervisor-node-exec.d.ts +39 -0
- package/dist/supervisor/supervisor-node-exec.d.ts.map +1 -0
- package/dist/supervisor/supervisor-node-exec.js +201 -0
- package/dist/supervisor/supervisor-node-exec.js.map +1 -0
- package/dist/supervisor/supervisor-orchestration.d.ts +24 -0
- package/dist/supervisor/supervisor-orchestration.d.ts.map +1 -0
- package/dist/supervisor/supervisor-orchestration.js +395 -0
- package/dist/supervisor/supervisor-orchestration.js.map +1 -0
- package/dist/supervisor/supervisor-portal.d.ts +75 -0
- package/dist/supervisor/supervisor-portal.d.ts.map +1 -0
- package/dist/supervisor/supervisor-portal.js +320 -0
- package/dist/supervisor/supervisor-portal.js.map +1 -0
- package/dist/supervisor/supervisor-route.d.ts +53 -0
- package/dist/supervisor/supervisor-route.d.ts.map +1 -0
- package/dist/supervisor/supervisor-route.js +308 -0
- package/dist/supervisor/supervisor-route.js.map +1 -0
- package/dist/supervisor/supervisor-special-turns.d.ts +34 -0
- package/dist/supervisor/supervisor-special-turns.d.ts.map +1 -0
- package/dist/supervisor/supervisor-special-turns.js +308 -0
- package/dist/supervisor/supervisor-special-turns.js.map +1 -0
- package/dist/supervisor/supervisor.d.ts +44 -0
- package/dist/supervisor/supervisor.d.ts.map +1 -0
- package/dist/supervisor/supervisor.js +79 -0
- package/dist/supervisor/supervisor.js.map +1 -0
- package/dist/supervisor/supervisor.types.d.ts +71 -0
- package/dist/supervisor/supervisor.types.d.ts.map +1 -0
- package/dist/supervisor/supervisor.types.js +8 -0
- package/dist/supervisor/supervisor.types.js.map +1 -0
- package/dist/tasks/studio-task-queue.d.ts +93 -0
- package/dist/tasks/studio-task-queue.d.ts.map +1 -0
- package/dist/tasks/studio-task-queue.js +226 -0
- package/dist/tasks/studio-task-queue.js.map +1 -0
- package/dist/util/studio-env.d.ts +7 -0
- package/dist/util/studio-env.d.ts.map +1 -0
- package/dist/util/studio-env.js +32 -0
- package/dist/util/studio-env.js.map +1 -0
- package/dist/vapi-studio.module.d.ts +55 -0
- package/dist/vapi-studio.module.d.ts.map +1 -0
- package/dist/vapi-studio.module.js +166 -0
- package/dist/vapi-studio.module.js.map +1 -0
- package/dist/workflow/workflow-handoff.service.d.ts +29 -0
- package/dist/workflow/workflow-handoff.service.d.ts.map +1 -0
- package/dist/workflow/workflow-handoff.service.js +165 -0
- package/dist/workflow/workflow-handoff.service.js.map +1 -0
- package/dist/workflow/workflow-loader.d.ts +48 -0
- package/dist/workflow/workflow-loader.d.ts.map +1 -0
- package/dist/workflow/workflow-loader.js +87 -0
- package/dist/workflow/workflow-loader.js.map +1 -0
- package/docs/README.md +69 -0
- package/docs/assets/deterministic-assistant-flow.svg +128 -0
- package/docs/assets/vapi-studio-stack.svg +26 -0
- package/docs/best-practices/README.md +37 -0
- package/docs/best-practices/brain-and-prompt-injection.md +70 -0
- package/docs/best-practices/conversation-design.md +152 -0
- package/docs/best-practices/debugging-and-observability.md +49 -0
- package/docs/best-practices/documentation-layers.md +31 -0
- package/docs/best-practices/identity-and-pii.md +66 -0
- package/docs/best-practices/nodes-and-listens.md +116 -0
- package/docs/best-practices/ui-and-api-identity.md +51 -0
- package/docs/building-apps/creating-an-app.md +59 -0
- package/docs/building-apps/example-apps.md +23 -0
- package/docs/building-apps/overview.md +27 -0
- package/docs/getting-started/installation.md +52 -0
- package/docs/getting-started/introduction.md +23 -0
- package/docs/getting-started/quick-start.md +23 -0
- package/docs/guide/concepts.md +103 -0
- package/docs/guide/forms.md +102 -0
- package/docs/guide/handbook.md +21 -0
- package/docs/guide/introduction.md +56 -0
- package/docs/guide/module-setup.md +68 -0
- package/docs/guide/vapi-adapter.md +70 -0
- package/docs/guide/workflow-squad.md +59 -0
- package/docs/guides/agents.md +85 -0
- package/docs/guides/docker-compose.events.stub.yaml +27 -0
- package/docs/guides/documentation.md +13 -0
- package/docs/guides/extending-events.md +155 -0
- package/docs/guides/node-conventions.md +59 -0
- package/docs/guides/speckit.md +56 -0
- package/docs/reference/data-model.md +79 -0
- package/docs/reference/environment-variables.md +60 -0
- package/docs/reference/events-and-logging.md +137 -0
- package/docs/reference/flow-schema.md +79 -0
- package/docs/reference/runtime-api.md +708 -0
- package/docs/reference/security.md +41 -0
- package/docs/reference/standard-intentions.md +49 -0
- package/docs/reference/testing.md +15 -0
- package/docs/samples/events/nest-listener.example.ts +17 -0
- package/docs/samples/events/redis-bridge.example.ts +44 -0
- package/package.json +81 -0
- package/scripts/fix-access-modifiers.mjs +85 -0
- package/scripts/install-agent-refs.cjs +196 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Identity and PII
|
|
2
|
+
|
|
3
|
+
Collecting caller identity without poisoning memory or stacking CTAs.
|
|
4
|
+
|
|
5
|
+
## Consent before destination
|
|
6
|
+
|
|
7
|
+
SMS / form delivery is two turns when both are needed:
|
|
8
|
+
|
|
9
|
+
1. **Consent** — “Is it okay if I text you a short form for your details?”
|
|
10
|
+
2. **Destination** — calling-from suitable? **or** best mobile?
|
|
11
|
+
|
|
12
|
+
Never ask consent and phone in one prompt. Decline → voice fallback path; do not re-ask consent after it is set for this Conversation.
|
|
13
|
+
|
|
14
|
+
## Phone numbers (NA default)
|
|
15
|
+
|
|
16
|
+
Unless the app explicitly supports other regions:
|
|
17
|
+
|
|
18
|
+
- Store **exactly 10 digits**.
|
|
19
|
+
- Strip leading country `1` when 11 digits.
|
|
20
|
+
- If more than 10 digit characters appear (trailing ASR), take the **first 10**.
|
|
21
|
+
- Fewer than 10 → fail and re-ask phone only.
|
|
22
|
+
- Use a **short** listen timeout while collecting digits.
|
|
23
|
+
- **Read-back confirm is mandatory** after spoken digits: “I heard 555-010-0999. Is that correct?” before SMS/form. Accepting calling-from ANI counts as confirm. Do not proceed on digits alone.
|
|
24
|
+
- Incomplete ASR (fewer than 10 digits) → say you only caught N digits and ask again — not a generic “sorry, what’s the number?”
|
|
25
|
+
- First ask for a missing field (e.g. mobile after consent) is a **gap fill**, not a re-ask — never preface with “I’m sorry, I didn’t quite get it.”
|
|
26
|
+
|
|
27
|
+
## Names
|
|
28
|
+
|
|
29
|
+
A person name is letters (and limited punctuation), not digits, not “hello”, not a full sentence. Reject and re-ask; never place digit strings into `firstName` / profile verify summaries (“I have 2 3 6 … on file”).
|
|
30
|
+
|
|
31
|
+
**Spoken name frequency:** personal treatment is fine; saying the first name **more than once per turn** (or in consecutive sentences across `say` + `continueTo`) usually sounds wrong. At most one use per assistant turn unless there is an explicit reason (re-engage after silence, empathy, disambiguation). Prefer shared helpers and a “name already used this segment” flag — see [conversation-design.md](./conversation-design.md#personal-treatment-names).
|
|
32
|
+
|
|
33
|
+
## Email
|
|
34
|
+
|
|
35
|
+
Boxed helpers — prefer the dedicated path:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import {
|
|
39
|
+
EMAIL_EXTRACT_DESCRIPTION,
|
|
40
|
+
collectedEmail,
|
|
41
|
+
parseSpelledEmail,
|
|
42
|
+
} from '@guidify-ai/vapi-studio/identity';
|
|
43
|
+
// also: from '@guidify-ai/vapi-studio'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
| Export | Role |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `EMAIL_EXTRACT_DESCRIPTION` | `extract.fields[].description` so Brain GOT knows spelled ASR (`at` / `dot` / letter-by-letter) |
|
|
49
|
+
| `collectedEmail(extracted, spoken)` | Prefer Brain extract, then spoken; `null` on fail |
|
|
50
|
+
| `parseSpelledEmail(raw)` | Direct `name@domain` **or** spelled forms |
|
|
51
|
+
|
|
52
|
+
Fail closed → re-ask the same CTA. Prefer a read-back confirm before sending mail (“I heard … Is that correct?”). Do not hardcode demo emails in production paths; PoC mocks belong behind clearly named constants and mock-only branches.
|
|
53
|
+
|
|
54
|
+
## Forms
|
|
55
|
+
|
|
56
|
+
- Deliver with `ctx.forms.open` (live Vapi) or `expose` (Studio-friendly blocking) + a dispose adapter that makes a fillable surface available (Studio modal, **first-party HTML** at `/forms/:exposeId`, or SMS link).
|
|
57
|
+
- Field specs are JSON (`FormFieldSpec`); HTML is rendered from that JSON (`renderFormHtml`), not hand-written per form.
|
|
58
|
+
- Submit must call `FormsService.submit(exposeId, values)` so the open path can `claimSubmitted()` (or blocking `expose` resumes).
|
|
59
|
+
- **Local PoC without SMS:** open `/forms/inbox` on this machine — it lists exposed / unsubmitted forms; Open → fill → Submit unblocks the waiting bot.
|
|
60
|
+
- **Caller never got the text / link:** while the form is still open, listen for “I didn’t get it / no text / didn’t receive the link.” Then **one CTA**: resend via `ctx.forms.resend()` and ask **“Did you get it this time?”** — do not also ask them to fill in the same turn. On yes → next turn asks them to open and fill. Cap resends (e.g. 2) then voice fallback. Never stack “resent” + “fill it out” + “still waiting” in one listen.
|
|
61
|
+
- Form deliver/fill failure → **one CTA**: “OK to finish by voice?” then collect fields. Never stack failure notice + first-name ask in one listen.
|
|
62
|
+
- After submit, voice-confirm received values when the channel is unreliable; do not treat unconfirmed ASR as identity.
|
|
63
|
+
|
|
64
|
+
## Profile verify
|
|
65
|
+
|
|
66
|
+
Only when CRM hydrated a **returning** caller (`returningCaller === true`, gated by `recognizeReturningCaller`). Never say “on file” for PII collected earlier in **this** Conversation. If recognition FF is off, treat every call as first-time for verify UX.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Nodes and listens
|
|
2
|
+
|
|
3
|
+
How to structure Vapi Studio Nodes so agents and humans can keep flows maintainable.
|
|
4
|
+
|
|
5
|
+
## Start → Greeting
|
|
6
|
+
|
|
7
|
+
Happy-path shape (how `/flow` should read, and how runtime should feel):
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
Start → Greeting (flow.start) → …
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
| Step | What it is | Runtime |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| **Start** | Call / bootstrap + inject bag + near-zero prep | `ConversationEntryPoint.createVariables` / `beforeEach` — company, email, name from LP; phone from caller; optional JWT/API warm. **Must not block first speech** (target < 1.5s to first SSE). Not a spoken Node; do not burn a Supervisor turn on silence. |
|
|
16
|
+
| **Greeting** | First real speak | `flow.start` Node — opening turn only (no Brain scan). Then the product DAG. |
|
|
17
|
+
|
|
18
|
+
**Do not** add Conversation-start or assistant-name bookends before Greeting on single-flow diagrams — Start is the entry. Squad/workflow lanes may still show per-module `__start` labels for handoff identity.
|
|
19
|
+
|
|
20
|
+
## Node = one job
|
|
21
|
+
|
|
22
|
+
Each Node should own a small slice: one ask, one verification, one side effect, or one lane step. If you need two sequential CTAs, use two Nodes (or `output.continueTo({ nodeId })`) — see [conversation-design.md](./conversation-design.md).
|
|
23
|
+
|
|
24
|
+
## Prefer `before()` / `after()` for async work
|
|
25
|
+
|
|
26
|
+
Do **not** overload `run()` with auth, CRM lookups, JWT warmups, or post-speak teardown. `run()` owns **speech + one terminal output** (`sayAndListen` / `continueTo` / `endCall` / …). Keep side effects on the lifecycle hooks:
|
|
27
|
+
|
|
28
|
+
| Hook | Role | Async guidance |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| **`before(ctx)`** | CAN gate + prepare | Authorize (`return false` to reject). **Prep / warm** integrations here. Prefer **spawn without awaiting** when the token is for *later* Nodes (do not block first SSE — target < 1.5s). `await` only when the gate itself needs the result. |
|
|
31
|
+
| **`listen(ctx)`** | Next listen binding | Register intentions / extract **before** speech; no I/O. |
|
|
32
|
+
| **`run(ctx)`** | Speak + terminal action | Copy and routing only. Do not nest “fetch then greet” unless the speak **cannot** proceed without the result. |
|
|
33
|
+
| **`after(ctx, result)`** | Teardown | Fire-and-forget analytics stamps that are not on the critical path, release handles, log completion. Must not speak. |
|
|
34
|
+
| **`catch(ctx, error)`** | Recovery | Only when overridden. |
|
|
35
|
+
|
|
36
|
+
Detached promises from `before()` should land in a Nest service / `Map<conversationId, Promise<…>>` (or similar), not assume late `ctx.memory` writes persist after the turn. Later Nodes `await` that shared promise when they need the value.
|
|
37
|
+
|
|
38
|
+
## `listen()` must match the ask
|
|
39
|
+
|
|
40
|
+
Whatever you just asked for, `listen()` should advertise:
|
|
41
|
+
|
|
42
|
+
- Intention names + boosts for that answer class
|
|
43
|
+
- `hints` for the Brain (short, operational)
|
|
44
|
+
- `extract` fields with **constraint-aware** `description`s when collecting data
|
|
45
|
+
- `resolveIntention` for cheap local matches (skip Brain when obvious) — include short answers like “new one” / “yes” / “ASAP” when the ask is constrained
|
|
46
|
+
- **Open-ended discovery:** prefer `INTENTION_CASCADE_PHASE.Scan` for the catch-all answer intention (so Brain can classify short/ambiguous turns). Use Match only for clear proceed phrases; keep FAQ as Force. `resolveIntention` should skip Brain for long substantive answers (≥ ~12 chars) and re-ask soft affirmatives locally — do **not** Match every ≥4-char utterance (that starves Scan)
|
|
47
|
+
- **Multi-choice soft affirmatives** (“yeah, why not”, bare “sure”) that do **not** name an option → re-ask which lane locally; never leave that to Brain/unknown ([conversation-design.md](./conversation-design.md#do-not-auto-pick-lanes))
|
|
48
|
+
- After a resolved binary fork (existing vs new, yes/no), **stamp memory** so the next menu advances; otherwise unknown recovery and re-prompts keep re-asking the same choice
|
|
49
|
+
- Unknown portal: Supervisor re-tries the **origin listen** on the next utterance before unknown’s own candidates — keep origin `resolveIntention` strong so restatements land on this turn
|
|
50
|
+
- `timeoutSeconds` when the answer type needs a non-default window
|
|
51
|
+
|
|
52
|
+
Do not leave a broad “collect name” listen active while you are asking for SMS consent.
|
|
53
|
+
|
|
54
|
+
## Extracts teach the Brain
|
|
55
|
+
|
|
56
|
+
`description` is part of the product. Spell the rule:
|
|
57
|
+
|
|
58
|
+
> North American mobile: exactly 10 digits. Prefer the first 10 digit characters; ignore trailing words. Fail if fewer than 10.
|
|
59
|
+
|
|
60
|
+
Local parsers and GOT must agree. Prefer framework boxed helpers from `@guidify-ai/vapi-studio/identity` (`collectedEmail`, `EMAIL_EXTRACT_DESCRIPTION`); app helpers (`collectedPhone`, `collectedPersonName`, …) for the rest — called from `onExtracted`.
|
|
61
|
+
|
|
62
|
+
## Memory writes are guarded
|
|
63
|
+
|
|
64
|
+
Never write PII from raw `userText` without a validator (`looksLikePersonName`, phone normalizer, email parser). Fail closed → re-ask the same CTA.
|
|
65
|
+
|
|
66
|
+
**Never speak untrusted input:** do not `say(ctx.userText)`, Brain `reason`, or judge `reasoning` to the caller. Off-topic / injection-shaped speech → `studio.isUnknownTransition` or structured recovery — see [brain-and-prompt-injection.md](./brain-and-prompt-injection.md).
|
|
67
|
+
|
|
68
|
+
**Never speak implementation labels:** node ids, class names, intention/transition names, event types, or graph state (“we were at X”) — see [conversation-design.md](./conversation-design.md) § Human-like UX. `listen()` hints/notes are Brain-only, not TTS.
|
|
69
|
+
|
|
70
|
+
## Flow jumps
|
|
71
|
+
|
|
72
|
+
| Action | When |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| `sayAndListen` | Stay on this Node; wait for the next user turn |
|
|
75
|
+
| `continueTo({ nodeId })` | Same Conversation, same flow, different Node (often same turn entry speak) — **silent** unless the destination needs a new product CTA; no filler restating the last beat ([conversation-design.md](./conversation-design.md#copy-hygiene)) |
|
|
76
|
+
| `handoff({ to })` | Workflow module switch (Squad / multi-assistant). Same silent-handoff rule. Only when a workflow is loaded |
|
|
77
|
+
|
|
78
|
+
Keep live PoCs on one assistant + `continueTo` unless you are deliberately testing Squad.
|
|
79
|
+
|
|
80
|
+
## Portals stay portals
|
|
81
|
+
|
|
82
|
+
Transfer, pause, mad, unknown, still-there, goodbye remain high-priority portals. Happy-path Nodes must not reimplement them. Unknown recovery copy can be shared with the unknown portal so menus stay consistent.
|
|
83
|
+
|
|
84
|
+
### Mad: one re-engage, then human (hard rule)
|
|
85
|
+
|
|
86
|
+
Do **not** keep reasoning with, apologizing to, or negotiating angry callers across multiple turns. Mad people escalate; a bot that stays in de-escalation loops wastes the call and makes things worse.
|
|
87
|
+
|
|
88
|
+
| Policy | Detail |
|
|
89
|
+
| --- | --- |
|
|
90
|
+
| **First mad hit** | One short re-engage (acknowledge + invite them to continue productively), then listen |
|
|
91
|
+
| **Second mad hit** (still heated) | **Transfer to a human** (or after-hours block) — no third apology loop |
|
|
92
|
+
| **Calm continue / goodbye** | May leave the portal without transferring |
|
|
93
|
+
| **Do not** | Invent long empathy scripts, argue, or keep `sayAndListen` de-escalation forever |
|
|
94
|
+
|
|
95
|
+
Framework note: mad is sticky (other portals suppressed while active) so the next utterance stays on the mad listen — that is for routing control, not an excuse to chat with anger. App mad Nodes must enforce the one-re-engage → transfer policy.
|
|
96
|
+
|
|
97
|
+
### Portal re-engage must end with one CTA (hard rule)
|
|
98
|
+
|
|
99
|
+
Any portal turn that **defers** the caller’s ask (transfer re-engage, mad first apology, after-hours block) must still obey **one CTA per turn**. Do not leave them on a dead-end statement.
|
|
100
|
+
|
|
101
|
+
| Bad | Good |
|
|
102
|
+
| --- | --- |
|
|
103
|
+
| “I understand you want a human, but I can resolve it for you.” *(no ask)* | “I can often take care of this without a transfer — **how can I help you?** Or say you'd still like a person.” |
|
|
104
|
+
| “I'm sorry you're frustrated.” *(stops)* | “I'm sorry you're dealing with this — **tell me what's going on**, and I'll help.” |
|
|
105
|
+
|
|
106
|
+
Listen for that turn must accept: insist again (transfer/mad), calm continue, **or** a product need if you offered help.
|
|
107
|
+
|
|
108
|
+
## Flow YAML is paths (+ optional condition transitions)
|
|
109
|
+
|
|
110
|
+
`flow.yaml` lists node ids, class names, intentions, portal flags, priority, and optional **`transitions`** (pure `when` gates → `to` node, with `force` to skip Brain). Copy and extract schemas live in Node source — not in YAML, not in app README northern stars.
|
|
111
|
+
|
|
112
|
+
**Prefer code intentions** (`INTENTION_CASCADE_PHASE.Force` \| `.Match` \| `.Scan`) when a gate needs memory/history/regex freedom — register via `VapiStudioModule.forRoot({ intentions })`. YAML transitions stay useful for diagram edges; code force intentions run first in the cascade.
|
|
113
|
+
|
|
114
|
+
Use **dedicated Nodes** for identity gap-fill (consent, phone, name). Declare the same gates as `transitions` and/or force intentions when you need the graph to show *why* the bot must leave a Node — duplicating a `continueTo` is fine.
|
|
115
|
+
|
|
116
|
+
`continueTo({ text })` is spoken on the wire — do not repeat copy (especially the caller’s name) that a prior `say` in the same pass already delivered. See [conversation-design.md](./conversation-design.md#personal-treatment-names).
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# UI and API identity
|
|
2
|
+
|
|
3
|
+
Hard rules for Studio UI ↔ Nest APIs and durable records. Applies to framework Studio SPA, app-served JSON, and any new FE surface.
|
|
4
|
+
|
|
5
|
+
## Rules (summary)
|
|
6
|
+
|
|
7
|
+
1. **External comms use UUID only** — paths, query params, request/response bodies, links, and client state identify records by `uuid`. Never send or accept numeric/`bigint` `id` from the FE (or any external client).
|
|
8
|
+
2. **BE stores `id` + `uuid`** — every durable entity has an internal integer/`bigint` primary key (`id`) for joins, filters, sorts, and indexes, and a unique `uuid` for all external identity. Services may use `id` internally; repositories that face HTTP resolve by `uuid`.
|
|
9
|
+
3. **Every exposed entity has a human `label`** — API DTOs return `label` so the UI can title rows, breadcrumbs, and headings **without** printing UUIDs. Prefer `label` in the UI; use `uuid` only as the opaque key (URL segment, React key, fetch param).
|
|
10
|
+
|
|
11
|
+
## Field contract
|
|
12
|
+
|
|
13
|
+
| Layer | Internal PK | External id | Human display |
|
|
14
|
+
| --- | --- | --- | --- |
|
|
15
|
+
| DB / TypeORM | `id` (`bigint` / serial) | `uuid` (unique, indexed) | `label` (varchar) and/or derived DTO `label` |
|
|
16
|
+
| HTTP JSON | **omit** | `uuid` | `label` (required on list + detail) |
|
|
17
|
+
| Studio UI | never | URL + API only | always show `label` |
|
|
18
|
+
|
|
19
|
+
Do **not** name the external field `id` in new APIs. Use `uuid` so clients never confuse it with the internal key.
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"uuid": "4a13e554-972b-4713-b208-15b71bff0493",
|
|
24
|
+
"label": "Sample Studio · ACTIVE · Sep 6, 1:30 PM",
|
|
25
|
+
"status": "ACTIVE"
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```http
|
|
30
|
+
GET /conversations/api/:uuid
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Not: `GET /conversations/api/42` or `{ "id": 42 }`.
|
|
34
|
+
|
|
35
|
+
## Labels
|
|
36
|
+
|
|
37
|
+
- **Stable enough to read** — short title a human understands (project name, caller display, “Studio call · ended”, channel + time).
|
|
38
|
+
- **Not a dump of the UUID** — never use the raw uuid string as `label`.
|
|
39
|
+
- **Computed OK** — if there is no stored column, the service may build `label` when mapping to the DTO (must still always be present on FE responses).
|
|
40
|
+
- **UI rule** — list rows, page titles, and toasts use `label`; monospace UUID only in operator “copy id” / debug affordances if needed.
|
|
41
|
+
|
|
42
|
+
## Backend practice
|
|
43
|
+
|
|
44
|
+
- Joins, `ORDER BY`, range filters, analytics aggregations → `id` (and indexed columns).
|
|
45
|
+
- `findByUuid` / route params → `uuid`.
|
|
46
|
+
- Never leak `id` in JSON, logs meant for operators who share URLs, or Vapi-facing payloads that the FE might echo.
|
|
47
|
+
- Nested resources: parent and child both identified by `uuid` in URLs (`/…/:conversationUuid/events/:eventUuid`).
|
|
48
|
+
|
|
49
|
+
## Migration note
|
|
50
|
+
|
|
51
|
+
Older tables may still use UUID as `@PrimaryGeneratedColumn('uuid')` named `id`. Treat that as transitional: new entities follow `id` + `uuid` + `label`; when touching persistence for an existing entity, migrate toward this shape and rename API fields to `uuid` + `label` in the same change.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Creating an application
|
|
2
|
+
|
|
3
|
+
Start from the public starter **[vapi-studio-project](https://github.com/guidify-ai/vapi-studio-project)**. That repo is the Nest shell for your bot: `flow.yaml`, Vapi routes, Docker, agent rules. This package (`@guidify-ai/vapi-studio`) is the framework it depends on — not an app template.
|
|
4
|
+
|
|
5
|
+
Do not put production secrets or customer flows in the framework repository.
|
|
6
|
+
|
|
7
|
+
## 1. Clone the starter
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
git clone git@github.com:guidify-ai/vapi-studio-project.git my-bot
|
|
11
|
+
cd my-bot
|
|
12
|
+
cp .env.example .env
|
|
13
|
+
# Edit PROJECT_NAME=… (PROJECT_SLUG optional — defaults from the name)
|
|
14
|
+
yarn install
|
|
15
|
+
yarn start # Docker Postgres + app + ngrok → prints Vapi URLs
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`yarn install` pulls `@guidify-ai/vapi-studio@0.1.0` and stamps Cursor/Claude rules + `AGENTS.md`. Postinstall syncs `config/project.identity.json` from `PROJECT_NAME` / `PROJECT_SLUG` (name + slug only).
|
|
19
|
+
|
|
20
|
+
### Live voice prerequisites
|
|
21
|
+
|
|
22
|
+
| Need | Detail |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| **Vapi** | Client has Vapi org access. `VAPI_API_KEY=` in `.env.example` is **required** (leave empty in the example; fill in `.env`). **Vapi is paid** — budget for Vapi usage/plan charges as well as Twilio. |
|
|
25
|
+
| **Twilio only (for now)** | Vapi supports other carriers; this stack currently assumes **Twilio**. Put `TWILIO_ACCOUNT_SID` / `TWILIO_AUTH_TOKEN` / `TWILIO_FROM_NUMBER` in the app `.env`. |
|
|
26
|
+
| **Trust Hub / +1** | Twilio Trust Hub + voice geo must allow outbound to **+1**. Trial / blocked geo → `Account not allowed to call +1…`. |
|
|
27
|
+
| **Balance** | Recommend ~**$30** with auto-recharge to $30 when balance hits ~$10. |
|
|
28
|
+
| **Same account** | `VAPI_PHONE_NUMBER_ID` must be a number imported in Vapi from **that** Twilio account. |
|
|
29
|
+
|
|
30
|
+
## 2. Wire Vapi
|
|
31
|
+
|
|
32
|
+
Paste the printed URLs into your Vapi assistant:
|
|
33
|
+
|
|
34
|
+
| Setting | Endpoint |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| **Webhook** | `{PUBLIC_BASE_URL}/vapi/webhook` |
|
|
37
|
+
| **Custom LLM** | `{PUBLIC_BASE_URL}/vapi/chat/completions` |
|
|
38
|
+
|
|
39
|
+
Each starter fork is its own deploy (own host / port / ngrok URL). Paths are host-scoped — no project UUID in the URL.
|
|
40
|
+
|
|
41
|
+
## 3. Build your conversation
|
|
42
|
+
|
|
43
|
+
1. Add `@Injectable()` classes extending `AgentNode<YourSchema>` under `src/conversation/`
|
|
44
|
+
2. Register them in `VapiStudioModule.forRoot({ nodes: [...] })` in `app.module.ts`
|
|
45
|
+
3. Reference them from `config/flow.yaml`
|
|
46
|
+
4. Grow `src/vapi/` as needed (webhook strategies, tools) — see the [showcase](https://github.com/guidify-ai/vapi-studio-landing-page-sample-model) for a full Planner PoC
|
|
47
|
+
|
|
48
|
+
Operator UI (`/flow`, `/conversations`) comes from the framework SPA mounted by the starter.
|
|
49
|
+
|
|
50
|
+
## 4. Keep going
|
|
51
|
+
|
|
52
|
+
- [Vapi adapter](../guide/vapi-adapter.md) — Custom LLM SSE + webhooks
|
|
53
|
+
- [Module setup](../guide/module-setup.md) — `VapiStudioModule.forRoot`
|
|
54
|
+
- [Best practices](../best-practices/README.md) — conversation design doctrine
|
|
55
|
+
- [Runtime API](../reference/runtime-api.md) — shipped contracts
|
|
56
|
+
|
|
57
|
+
## Framework contributors
|
|
58
|
+
|
|
59
|
+
Clone [vapi-studio](https://github.com/guidify-ai/vapi-studio) only when changing the library itself (`yarn build`, `yarn test`). Apps always start from [vapi-studio-project](https://github.com/guidify-ai/vapi-studio-project).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Example applications
|
|
2
|
+
|
|
3
|
+
## Starter (start here)
|
|
4
|
+
|
|
5
|
+
**[vapi-studio-project](https://github.com/guidify-ai/vapi-studio-project)** — blank Nest shell for a new bot.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone git@github.com:guidify-ai/vapi-studio-project.git my-bot
|
|
9
|
+
cd my-bot && cp .env.example .env
|
|
10
|
+
# Edit PROJECT_NAME=…
|
|
11
|
+
yarn install && yarn start
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Surface after `yarn start`:
|
|
15
|
+
|
|
16
|
+
- Operator SPA: `/flow`, `/conversations`
|
|
17
|
+
- Ingress: `/vapi/webhook`, `/vapi/chat/completions` (host-scoped)
|
|
18
|
+
|
|
19
|
+
Step-by-step: [Creating an app](./creating-an-app.md).
|
|
20
|
+
|
|
21
|
+
## Showcase (reference implementation)
|
|
22
|
+
|
|
23
|
+
**[vapi-studio-landing-page-sample-model](https://github.com/guidify-ai/vapi-studio-landing-page-sample-model)** — Planner LLM demo (not a blank starter). Depends on `@guidify-ai/vapi-studio@0.1.0` (or `file:../vapi-studio` while developing the next package version). Use it to see richer Vapi strategies, funnels, and multi-node flows.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Building applications — overview
|
|
2
|
+
|
|
3
|
+
**[@guidify-ai/vapi-studio](https://www.npmjs.com/package/@guidify-ai/vapi-studio)** is an npm library. Your bot is a NestJS app that depends on it.
|
|
4
|
+
|
|
5
|
+
**Start every new bot by cloning [vapi-studio-project](https://github.com/guidify-ai/vapi-studio-project)** — then rename, set `PROJECT_NAME`, and extend nodes / `flow.yaml`.
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
my-bot/ ← clone of vapi-studio-project
|
|
9
|
+
├── package.json ← "@guidify-ai/vapi-studio": "0.1.0"
|
|
10
|
+
├── config/
|
|
11
|
+
│ ├── project.identity.json ← name + slug (from .env)
|
|
12
|
+
│ └── flow.yaml
|
|
13
|
+
├── src/
|
|
14
|
+
│ ├── conversation/ ← your agent steps
|
|
15
|
+
│ └── vapi/ ← /vapi/webhook + /vapi/chat/completions
|
|
16
|
+
└── docker-compose.yaml
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
| Repo | Role |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| [vapi-studio](https://github.com/guidify-ai/vapi-studio) | Framework → npm `@guidify-ai/vapi-studio` |
|
|
22
|
+
| [vapi-studio-project](https://github.com/guidify-ai/vapi-studio-project) | **Starter** — fork this to ship a bot |
|
|
23
|
+
| [vapi-studio-landing-page-sample-model](https://github.com/guidify-ai/vapi-studio-landing-page-sample-model) | Showcase (Planner LLM) — study patterns, not a blank template |
|
|
24
|
+
|
|
25
|
+
Do not add customer-specific agent steps under the framework `src/` — only in your starter fork.
|
|
26
|
+
|
|
27
|
+
[Creating an app](./creating-an-app.md) · [Example apps](./example-apps.md)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Installation
|
|
2
|
+
|
|
3
|
+
## New bot (recommended)
|
|
4
|
+
|
|
5
|
+
You do **not** install the framework into an empty folder by hand. Clone the starter — it already depends on the package:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone git@github.com:guidify-ai/vapi-studio-project.git my-bot
|
|
9
|
+
cd my-bot
|
|
10
|
+
cp .env.example .env
|
|
11
|
+
yarn install # installs @guidify-ai/vapi-studio@0.1.0 + stamps agent rules
|
|
12
|
+
yarn start
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Starter: [guidify-ai/vapi-studio-project](https://github.com/guidify-ai/vapi-studio-project)
|
|
16
|
+
Package: [npm `@guidify-ai/vapi-studio`](https://www.npmjs.com/package/@guidify-ai/vapi-studio)
|
|
17
|
+
|
|
18
|
+
Vapi Studio apps are **self-hosted** and **Dockerized**. There is no Guidify-hosted runtime. Vapi is the cloud voice channel; your containers serve Custom LLM + webhook.
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- **Node.js 22+**
|
|
23
|
+
- **Yarn 1.x** or npm
|
|
24
|
+
- **Docker** + Compose
|
|
25
|
+
- **[ngrok](https://ngrok.com/download)** for local HTTPS to Vapi
|
|
26
|
+
- A **Vapi** account
|
|
27
|
+
|
|
28
|
+
On install, the package **postinstall** stamps agent rules (`AGENTS.md`, `.cursor/rules/…`) into the starter when present.
|
|
29
|
+
|
|
30
|
+
## What is published (and what is not)
|
|
31
|
+
|
|
32
|
+
The npm tarball includes **`dist/`**, **`docs/`**, **`agent/`**, **`scripts/`**, **`LICENSE`**, **`README.md`**. It does **not** include `.env`, secrets, or application bots.
|
|
33
|
+
|
|
34
|
+
## Contribute to the framework (optional)
|
|
35
|
+
|
|
36
|
+
Only when changing the library itself:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
git clone git@github.com:guidify-ai/vapi-studio.git
|
|
40
|
+
cd vapi-studio
|
|
41
|
+
yarn install
|
|
42
|
+
yarn build
|
|
43
|
+
yarn test
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Agent refs (manual)
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
INIT_CWD=$PWD node node_modules/@guidify-ai/vapi-studio/scripts/install-agent-refs.cjs
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Next: [Quick start](./quick-start.md) · [Creating an app](../building-apps/creating-an-app.md)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Introduction
|
|
2
|
+
|
|
3
|
+
**Vapi Studio** (`@guidify-ai/vapi-studio`) is a NestJS toolkit for **deterministic voice agents** on [Vapi](https://vapi.ai).
|
|
4
|
+
|
|
5
|
+
Your bot is a **separate NestJS app**. Start from **[vapi-studio-project](https://github.com/guidify-ai/vapi-studio-project)** — clone it, set `PROJECT_NAME`, then own nodes, `flow.yaml`, secrets, and Docker.
|
|
6
|
+
|
|
7
|
+
## What you get
|
|
8
|
+
|
|
9
|
+
- Typed agent steps (`AgentNode`), Supervisor routing, optional Brain at listen boundaries
|
|
10
|
+
- Forms, tasks, event bus, Studio operator SPA assets
|
|
11
|
+
- Docs and agent rules stamped into consumer apps on install
|
|
12
|
+
|
|
13
|
+
## What you build
|
|
14
|
+
|
|
15
|
+
A fork of [vapi-studio-project](https://github.com/guidify-ai/vapi-studio-project) that depends on `@guidify-ai/vapi-studio@0.1.0`, with Vapi Custom LLM + webhook at `/vapi/...` on your host.
|
|
16
|
+
|
|
17
|
+
## What this is not
|
|
18
|
+
|
|
19
|
+
- A no-code builder or open-ended LLM agent platform
|
|
20
|
+
- A Guidify-hosted runtime (you self-host)
|
|
21
|
+
- A monorepo of customer bots (each bot is its own starter fork)
|
|
22
|
+
|
|
23
|
+
Next: [Installation](./installation.md) · [Quick start](./quick-start.md) · [Concepts](../guide/concepts.md)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Quick start
|
|
2
|
+
|
|
3
|
+
Clone the public starter, name the project, run it.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
git clone git@github.com:guidify-ai/vapi-studio-project.git my-bot
|
|
7
|
+
cd my-bot
|
|
8
|
+
cp .env.example .env
|
|
9
|
+
# Edit PROJECT_NAME=… (PROJECT_SLUG optional)
|
|
10
|
+
yarn install
|
|
11
|
+
yarn start # Docker + Postgres + ngrok → prints Vapi URLs
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Paste into your Vapi assistant:
|
|
15
|
+
|
|
16
|
+
| Setting | URL |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| Webhook | `{PUBLIC_BASE_URL}/vapi/webhook` |
|
|
19
|
+
| Custom LLM | `{PUBLIC_BASE_URL}/vapi/chat/completions` |
|
|
20
|
+
|
|
21
|
+
Then extend `src/conversation/` and `config/flow.yaml`. Full walkthrough: [Creating an app](../building-apps/creating-an-app.md).
|
|
22
|
+
|
|
23
|
+
Framework contributors (changing the library, not starting a bot): clone [vapi-studio](https://github.com/guidify-ai/vapi-studio), `yarn build`, `yarn test` — see [Installation](./installation.md).
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Concepts
|
|
2
|
+
|
|
3
|
+
Mental model for Vapi Studio. API detail: [Runtime API](../reference/runtime-api.md).
|
|
4
|
+
|
|
5
|
+
## Conversation
|
|
6
|
+
|
|
7
|
+
A **Conversation** is one caller session:
|
|
8
|
+
|
|
9
|
+
- **Framework-generated `conversationId`** — canonical id (never replace with Vapi call id)
|
|
10
|
+
- **Variables** — seeded once at entry (channel, caller id, feature flags)
|
|
11
|
+
- **Memory** — mutable working state for the call
|
|
12
|
+
- **History** — turn log for Brain and forensics
|
|
13
|
+
- **Postgres row** — durable lifecycle; in-memory `SupervisedConversation` for active calls
|
|
14
|
+
|
|
15
|
+
## Turn lifecycle
|
|
16
|
+
|
|
17
|
+
Each user utterance (or silence timeout) triggers the **Supervisor** routing cascade:
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
1. Force intentions (code + YAML transitions)
|
|
21
|
+
2. Condition transitions (memory expressions)
|
|
22
|
+
3. Match intentions on current node
|
|
23
|
+
4. Listen resolve (extract / resolveIntention — may skip Brain)
|
|
24
|
+
5. Brain scan (portal + node intentions)
|
|
25
|
+
6. Walk nodes (boot via before(), run)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
On failure to route, the framework falls back to `studio.isUnknownTransition` rather than throwing.
|
|
29
|
+
|
|
30
|
+
## Agent steps
|
|
31
|
+
|
|
32
|
+
Agent steps are NestJS injectable classes extending `AgentNode`:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
export class AskNameNode extends AgentNode<MySchema> {
|
|
36
|
+
async before(ctx) { return true; } // CAN gate
|
|
37
|
+
async listen(ctx) { /* register extract */ }
|
|
38
|
+
async run(ctx) {
|
|
39
|
+
return ctx.output.sayAndListen("What's your first name?", {
|
|
40
|
+
extract: { fields: [{ key: 'firstName', /* … */ }] },
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Declare methods in **runtime order**: `before` → `listen` → `run` → `after` → `catch`.
|
|
47
|
+
|
|
48
|
+
Agent steps speak only through `ctx.output` — never Vapi JSON directly.
|
|
49
|
+
|
|
50
|
+
## Flow YAML
|
|
51
|
+
|
|
52
|
+
`config/flow.yaml` declares:
|
|
53
|
+
|
|
54
|
+
- **start** node id
|
|
55
|
+
- **nodes** — class binding, intentions, `portal`, `priority`, `terminal`
|
|
56
|
+
- **transitions** — conditional jumps (`when`, `force`, `reason`)
|
|
57
|
+
- **portals** — global interrupts (goodbye, transfer, still-there, mad)
|
|
58
|
+
|
|
59
|
+
Lane changes inside one assistant use `ctx.output.continueTo({ nodeId })`.
|
|
60
|
+
|
|
61
|
+
## Intentions
|
|
62
|
+
|
|
63
|
+
Named signals routing the supervisor:
|
|
64
|
+
|
|
65
|
+
- **Standard** — `studio.isGoodbye`, `studio.isTransferToHuman`, `studio.isStillThere`, …
|
|
66
|
+
- **App** — `isAcceptedAppointment`, `isDidNotReceiveForm`, …
|
|
67
|
+
- **Code intentions** — `CodeIntention` with `INTENTION_CASCADE_PHASE` (`Force` \| `Match` \| `Scan`; see [Runtime API](../reference/runtime-api.md))
|
|
68
|
+
|
|
69
|
+
## Brain
|
|
70
|
+
|
|
71
|
+
`BrainService` implements `scan` / `clarify` / `judge`. Model and confidence threshold are set in **`VapiStudioModule.forRoot({ brain })`** — not env vars.
|
|
72
|
+
|
|
73
|
+
Live-call rules: no pre-scan listen-hold on ChatGPT path; scan abort ≤ 3s; target first speech < 1.5s.
|
|
74
|
+
|
|
75
|
+
## Vapi integration
|
|
76
|
+
|
|
77
|
+
Vapi Custom LLM and webhooks translate wire format ↔ Vapi Studio I/O.
|
|
78
|
+
|
|
79
|
+
- **Live calls** — SSE streaming, tool calls for end/transfer/handoff
|
|
80
|
+
- **Flow Studio** — HTTP text harness on the same supervisor (application feature for local dev)
|
|
81
|
+
|
|
82
|
+
## Forms
|
|
83
|
+
|
|
84
|
+
`ctx.forms.open()` (live Vapi) or `expose()` (blocking Studio) + a **dispose adapter** delivers fillable surfaces. Submit → `claimSubmitted()` or blocking resume.
|
|
85
|
+
|
|
86
|
+
## Events
|
|
87
|
+
|
|
88
|
+
Vapi Studio is **event-driven**. `EventService` emits `StudioEvent`s (`STUDIO_EVENTS` constants) onto the console, an in-process Node bus (`onStudioEvent`), and optional Nest `eventListeners`. Attach custom listeners for any logic you need — Guidify companion tools use the same hooks; OSS does not limit what you build there. Logs must explain **why** a turn routed. See [Events & logging](../reference/events-and-logging.md) · [Extending events](../guides/extending-events.md).
|
|
89
|
+
|
|
90
|
+
## Conversation limits
|
|
91
|
+
|
|
92
|
+
Every Conversation has always-on caps (`limits.maxTurns` / `limits.maxDurationMs`, defaults 40 / 20m). Fail-closed goodbye + `endCall` when exceeded — see [Runtime API](../reference/runtime-api.md) and [conversation design](../best-practices/conversation-design.md).
|
|
93
|
+
|
|
94
|
+
## Resume & recovery (post-MVP)
|
|
95
|
+
|
|
96
|
+
Framework ships optional helpers; **MVP apps start fresh each call** unless you explicitly opt in:
|
|
97
|
+
|
|
98
|
+
- **Cross-call resume** — `ConversationResumeService` (same caller within ~10 minutes)
|
|
99
|
+
- **Crash recovery** — `restoreAllActive()` on process boot (single-process; not multi-node failover)
|
|
100
|
+
|
|
101
|
+
## Workflows (optional)
|
|
102
|
+
|
|
103
|
+
Multi-assistant **Squad** handoffs via `workflow.yaml` — see [Workflow & Squad](./workflow-squad.md). Many apps use **one assistant** and `continueTo` lanes instead.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Forms
|
|
2
|
+
|
|
3
|
+
Structured collection outside the voice channel — SMS links, Studio modals, first-party HTML.
|
|
4
|
+
|
|
5
|
+
## API surface (`ctx.forms`)
|
|
6
|
+
|
|
7
|
+
| Method | Use when |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `expose(spec)` | **Blocking** — holds the channel turn until submit (Flow Studio modal) |
|
|
10
|
+
| `open(spec)` | **Non-blocking** — deliver + speak, finish SSE turn, wait for submit later (live Vapi) |
|
|
11
|
+
| `resend()` | Caller did not receive SMS/link — re-run dispose on same `exposeId` |
|
|
12
|
+
| `claimSubmitted()` | Take values after `open()` submit |
|
|
13
|
+
| `hasPending()` | Open expose awaiting submit (ghosting must no-op) |
|
|
14
|
+
| `hasUnclaimedSubmit()` | Submitted but not yet claimed |
|
|
15
|
+
|
|
16
|
+
## Dispose adapter
|
|
17
|
+
|
|
18
|
+
Apps provide `FormDisposeAdapter`:
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
@Injectable()
|
|
22
|
+
export class LinkFormDisposeAdapter implements FormDisposeAdapter {
|
|
23
|
+
readonly id = 'studio-first-party-html';
|
|
24
|
+
readonly branch = 'html_link'; // delivery lane for forensics
|
|
25
|
+
|
|
26
|
+
async dispose(payload: FormDisposePayload): Promise<void> {
|
|
27
|
+
// Publish URL, send SMS, open Studio modal…
|
|
28
|
+
forms.ack(payload.exposeId);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`disposeContext` on expose is opaque app data (e.g. `contactPhone`, `channel`) stored for `resend()`.
|
|
34
|
+
|
|
35
|
+
### Twilio SMS (prepared)
|
|
36
|
+
|
|
37
|
+
Framework ships `TwilioSmsFormDisposeAdapter` (`branch: sms`). Wire it when you want SMS link delivery:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
VapiStudioModule.forRoot({
|
|
41
|
+
formDisposeAdapter: TwilioSmsFormDisposeAdapter,
|
|
42
|
+
// …
|
|
43
|
+
})
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Reserved env (default **dry-run on** — no live Twilio calls until you opt in):
|
|
47
|
+
|
|
48
|
+
| Variable | Purpose |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| `TWILIO_ACCOUNT_SID` | Account SID |
|
|
51
|
+
| `TWILIO_AUTH_TOKEN` | Auth token |
|
|
52
|
+
| `TWILIO_FROM_NUMBER` | E.164 from-number **or** |
|
|
53
|
+
| `TWILIO_MESSAGING_SERVICE_SID` | Messaging Service SID |
|
|
54
|
+
| `TWILIO_SMS_DRY_RUN` | Default `1` — still emits durable `OUTBOUND_NOTIFICATION` (`status: dry_run`) and ACKs without API. Set `0` for live send (requires peer package `twilio`) |
|
|
55
|
+
|
|
56
|
+
Each send (dry-run or live) goes through `EventService.persist` as `OUTBOUND_NOTIFICATION` (in-process Node bus + Nest listeners). Failures emit `OUTBOUND_NOTIFICATION_ERROR`.
|
|
57
|
+
|
|
58
|
+
`disposeContext` for this driver:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
disposeContext: {
|
|
62
|
+
contactPhone: '5550100999', // or E.164 / `to` / `phone`
|
|
63
|
+
// formUrl optional — defaults to `{PUBLIC_BASE_URL}/forms/{exposeId}`
|
|
64
|
+
// body?: 'optional custom SMS text'
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Set `PUBLIC_BASE_URL` (ngrok/public HTTPS origin) so the texted link is reachable. Override with an explicit `formUrl` when the fill page lives elsewhere.
|
|
69
|
+
|
|
70
|
+
## Events
|
|
71
|
+
|
|
72
|
+
| Event | When |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| `FORM_SENDOUT` | Early — branch + delivery lane before ACK |
|
|
75
|
+
| `FORM_EXPOSE` / `FORM_DELIVERED` | Adapter delivered |
|
|
76
|
+
| `FORM_RESEND` / `FORM_RESENT` | Caller never got link |
|
|
77
|
+
| `FORM_SUBMITTED` | Values received |
|
|
78
|
+
| `FORM_DELIVER_TIMEOUT` / `FORM_FILLOUT_TIMEOUT` | Timeouts |
|
|
79
|
+
|
|
80
|
+
## HTML helpers
|
|
81
|
+
|
|
82
|
+
- `renderFormHtml(handle)` — JSON fields → fillable page
|
|
83
|
+
- `renderFormThanksHtml()` / `renderFormGoneHtml()`
|
|
84
|
+
|
|
85
|
+
Apps own HTTP routes (`GET/POST /forms/:exposeId`). Document routes in the application repository.
|
|
86
|
+
|
|
87
|
+
## Timeouts
|
|
88
|
+
|
|
89
|
+
- **ACK** — `STUDIO_FORM_ACK_MS` (default 15s) after dispose
|
|
90
|
+
- **Fillout** — `filloutTimeoutMs` on expose spec (open path timer)
|
|
91
|
+
|
|
92
|
+
## Doctrine
|
|
93
|
+
|
|
94
|
+
- Form fail → **one CTA**: voice fallback
|
|
95
|
+
- “Didn’t get text” → resend + “did you get it this time?” — not stacked with fill prompts
|
|
96
|
+
|
|
97
|
+
Full rules: [Identity and PII](../best-practices/identity-and-pii.md)
|
|
98
|
+
|
|
99
|
+
## Related
|
|
100
|
+
|
|
101
|
+
- [Runtime API](../reference/runtime-api.md) — forms contracts
|
|
102
|
+
- [Identity and PII](../best-practices/identity-and-pii.md)
|