dv-pipecat-flows 0.0.23.dev7__tar.gz → 0.0.23.dev9__tar.gz

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.
Files changed (92) hide show
  1. {dv_pipecat_flows-0.0.23.dev7/src/dv_pipecat_flows.egg-info → dv_pipecat_flows-0.0.23.dev9}/PKG-INFO +1 -2
  2. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/README.md +0 -1
  3. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9/src/dv_pipecat_flows.egg-info}/PKG-INFO +1 -2
  4. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/src/dv_pipecat_flows.egg-info/scm_file_list.json +19 -0
  5. dv_pipecat_flows-0.0.23.dev9/src/dv_pipecat_flows.egg-info/scm_version.json +8 -0
  6. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/src/pipecat_flows/actions.py +42 -7
  7. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/src/pipecat_flows/router_mode.py +12 -3
  8. dv_pipecat_flows-0.0.23.dev7/src/dv_pipecat_flows.egg-info/scm_version.json +0 -8
  9. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/.agents/skills/loki-logs/SKILL.md +0 -0
  10. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/.agents/skills/loki-logs/query-reference.md +0 -0
  11. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/.claude/skills/loki-logs/SKILL.md +0 -0
  12. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/.claude/skills/loki-logs/query-reference.md +0 -0
  13. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/.gitattributes +0 -0
  14. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/.gitignore +0 -0
  15. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/.pre-commit-config.yaml +0 -0
  16. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/.python-version +0 -0
  17. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/.readthedocs.yaml +0 -0
  18. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/AGENTS.md +0 -0
  19. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/CHANGELOG.md +0 -0
  20. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/CLAUDE.md +0 -0
  21. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/CONTRIBUTING.md +0 -0
  22. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/LICENSE +0 -0
  23. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/MANIFEST.in +0 -0
  24. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/dev-requirements.txt +0 -0
  25. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/docker-compose.dev.yml +0 -0
  26. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/.eslintrc.json +0 -0
  27. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/.prettierrc +0 -0
  28. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/css/tailwind.css +0 -0
  29. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/examples/food_ordering.json +0 -0
  30. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/examples/movie_explorer.json +0 -0
  31. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/examples/patient_intake.json +0 -0
  32. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/examples/restaurant_reservation.json +0 -0
  33. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/examples/travel_planner.json +0 -0
  34. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/favicon.png +0 -0
  35. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/favicon.svg +0 -0
  36. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/index.html +0 -0
  37. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/js/editor/canvas.js +0 -0
  38. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/js/editor/editorState.js +0 -0
  39. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/js/editor/sidePanel.js +0 -0
  40. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/js/editor/toolbar.js +0 -0
  41. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/js/main.js +0 -0
  42. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/js/nodes/baseNode.js +0 -0
  43. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/js/nodes/endNode.js +0 -0
  44. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/js/nodes/flowNode.js +0 -0
  45. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/js/nodes/functionNode.js +0 -0
  46. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/js/nodes/index.js +0 -0
  47. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/js/nodes/mergeNode.js +0 -0
  48. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/js/nodes/startNode.js +0 -0
  49. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/js/types.js +0 -0
  50. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/js/utils/export.js +0 -0
  51. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/js/utils/helpers.js +0 -0
  52. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/js/utils/import.js +0 -0
  53. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/js/utils/validation.js +0 -0
  54. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/jsdoc.json +0 -0
  55. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/package-lock.json +0 -0
  56. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/package.json +0 -0
  57. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/postcss.config.cjs +0 -0
  58. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/public/favicon.png +0 -0
  59. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/public/favicon.svg +0 -0
  60. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/tailwind.config.cjs +0 -0
  61. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/vercel.json +0 -0
  62. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/editor/vite.config.js +0 -0
  63. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/engine_primitives_plan.md +0 -0
  64. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/env.example +0 -0
  65. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/images/food-ordering-flow.png +0 -0
  66. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/pipecat-flows.png +0 -0
  67. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/pipecat_upgrade.md +0 -0
  68. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/pyproject.toml +0 -0
  69. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/remote-asterisk-code/README.md +0 -0
  70. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/remote-asterisk-code/extensions.conf +0 -0
  71. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/remote-asterisk-code/rtp.conf +0 -0
  72. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/requirements.txt +0 -0
  73. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/scripts/check-pypi-package.py +0 -0
  74. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/scripts/fix-ruff.sh +0 -0
  75. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/scripts/pre-commit.sh +0 -0
  76. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/setup.cfg +0 -0
  77. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/src/dv_pipecat_flows.egg-info/SOURCES.txt +0 -0
  78. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/src/dv_pipecat_flows.egg-info/dependency_links.txt +0 -0
  79. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/src/dv_pipecat_flows.egg-info/requires.txt +0 -0
  80. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/src/dv_pipecat_flows.egg-info/top_level.txt +0 -0
  81. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/src/pipecat_flows/__init__.py +0 -0
  82. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/src/pipecat_flows/adapters.py +0 -0
  83. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/src/pipecat_flows/condition_evaluator.py +0 -0
  84. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/src/pipecat_flows/exceptions.py +0 -0
  85. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/src/pipecat_flows/flow_validator.py +0 -0
  86. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/src/pipecat_flows/manager.py +0 -0
  87. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/src/pipecat_flows/processors/__init__.py +0 -0
  88. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/src/pipecat_flows/processors/router_mode_guard.py +0 -0
  89. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/src/pipecat_flows/processors/speak_interruption_guard.py +0 -0
  90. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/src/pipecat_flows/processors/user_turn_observer.py +0 -0
  91. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/src/pipecat_flows/types.py +0 -0
  92. {dv_pipecat_flows-0.0.23.dev7 → dv_pipecat_flows-0.0.23.dev9}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dv-pipecat-flows
3
- Version: 0.0.23.dev7
3
+ Version: 0.0.23.dev9
4
4
  Summary: Conversation Flow management for Pipecat AI applications
5
5
  License: BSD 2-Clause License
6
6
  Project-URL: Source, https://github.com/pipecat-ai/pipecat-flows
@@ -309,4 +309,3 @@ We aim to review all contributions promptly and provide constructive feedback to
309
309
  ➡️ [Reach us on X](https://x.com/pipecat_ai)
310
310
 
311
311
 
312
-
@@ -284,4 +284,3 @@ We aim to review all contributions promptly and provide constructive feedback to
284
284
  ➡️ [Reach us on X](https://x.com/pipecat_ai)
285
285
 
286
286
 
287
-
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dv-pipecat-flows
3
- Version: 0.0.23.dev7
3
+ Version: 0.0.23.dev9
4
4
  Summary: Conversation Flow management for Pipecat AI applications
5
5
  License: BSD 2-Clause License
6
6
  Project-URL: Source, https://github.com/pipecat-ai/pipecat-flows
@@ -309,4 +309,3 @@ We aim to review all contributions promptly and provide constructive feedback to
309
309
  ➡️ [Reach us on X](https://x.com/pipecat_ai)
310
310
 
311
311
 
312
-
@@ -22,6 +22,7 @@
22
22
  ".readthedocs.yaml",
23
23
  "env.example",
24
24
  "images/food-ordering-flow.png",
25
+ "k8s/docs/deepgram-self-hosted-restore.md",
25
26
  "k8s/livekit/Chart.yaml",
26
27
  "k8s/livekit/README.md",
27
28
  "k8s/livekit/values.yaml",
@@ -93,10 +94,13 @@
93
94
  "docs/ASTERISK_OUTBOUND_CHAN_WEBSOCKET.md",
94
95
  "docs/ASTERISK_MACHINE_SETUP.md",
95
96
  "docs/CALL_TRANSFER_SUMMARY_DESIGN.md",
97
+ "docs/blockkit-agents-cdn-integration.md",
96
98
  "docs/make.bat",
97
99
  "docs/build-docs.sh",
98
100
  "docs/AZURE_MULTI_REGION_ROUTING.md",
99
101
  "docs/examples/pb_fintech_call_transfer_config.json",
102
+ "docs/superpowers/plans/2026-07-11-timeout-connection-diagnostics.md",
103
+ "docs/superpowers/specs/2026-07-11-timeout-connection-diagnostics-design.md",
100
104
  "ringg-bot/server.py",
101
105
  "ringg-bot/pipeline_test.py",
102
106
  "ringg-bot/pytest.ini",
@@ -158,6 +162,7 @@
158
162
  "ringg-bot/ringg_processors/openai_realtime_transcript_filter.py",
159
163
  "ringg-bot/ringg_processors/realtime_tts_bridge.py",
160
164
  "ringg-bot/ringg_processors/two_stage_user_idle_processor.py",
165
+ "ringg-bot/ringg_processors/realtime_audio_suppressor.py",
161
166
  "ringg-bot/ringg_processors/function_call_filler_processor.py",
162
167
  "ringg-bot/ringg_processors/screen_auto_injector.py",
163
168
  "ringg-bot/ringg_processors/livekit_caption_observer.py",
@@ -209,13 +214,19 @@
209
214
  "ringg-bot/voice_services/twilio/router.py",
210
215
  "ringg-bot/voice_services/twilio/twilio_service.py",
211
216
  "ringg-bot/transports/factory.py",
217
+ "ringg-bot/tests/test_http_client_diagnostics.py",
212
218
  "ringg-bot/tests/test_tts_currency.py",
213
219
  "ringg-bot/tests/test_azure_region_routing.py",
214
220
  "ringg-bot/tests/test_sdk_tool_schema.py",
221
+ "ringg-bot/tests/test_recording_stereo.py",
215
222
  "ringg-bot/tests/conftest.py",
223
+ "ringg-bot/tests/test_widget_text_suppression.py",
216
224
  "ringg-bot/tests/test_inbound_registration_race.py",
225
+ "ringg-bot/tests/test_action_call_variables.py",
226
+ "ringg-bot/tests/test_send_widget_action.py",
217
227
  "ringg-bot/tests/test_language_map.py",
218
228
  "ringg-bot/tests/test_warning_fixed_message.py",
229
+ "ringg-bot/tests/test_transcript_timestamp_format.py",
219
230
  "ringg-bot/tests/test_variable_replacement.py",
220
231
  "ringg-bot/tests/test_metrics_bridge.py",
221
232
  "ringg-bot/tests/test_language_steering.py",
@@ -224,12 +235,15 @@
224
235
  "ringg-bot/tests/test_word_timestamp_logger.py",
225
236
  "ringg-bot/tests/__init__.py",
226
237
  "ringg-bot/tests/test_kb_planner.py",
238
+ "ringg-bot/tests/test_inbound_call_data_and_custom_vars.py",
227
239
  "ringg-bot/tests/test_whatsapp.py",
228
240
  "ringg-bot/tests/test_recording_flush.py",
229
241
  "ringg-bot/tests/test_metrics_collector_llm.py",
230
242
  "ringg-bot/tests/test_language_sample_store.py",
231
243
  "ringg-bot/tests/test_traffic_split.py",
232
244
  "ringg-bot/tests/test_flow_query_kb.py",
245
+ "ringg-bot/tests/test_realtime_external_tts.py",
246
+ "ringg-bot/tests/test_send_blocks_calendar.py",
233
247
  "ringg-bot/tests/test_sdk_executor_flatten.py",
234
248
  "ringg-bot/tests/test_decimal_safe_aggregator.py",
235
249
  "ringg-bot/tests/test_dropped_turn_regeneration.py",
@@ -239,7 +253,10 @@
239
253
  "ringg-bot/tests/test_summary_dispatcher.py",
240
254
  "ringg-bot/tests/test_ozonetel_transfer.py",
241
255
  "ringg-bot/tests/test_tool_executor_context.py",
256
+ "ringg-bot/tests/test_media_mode.py",
257
+ "ringg-bot/tests/test_widget_tool_tracking.py",
242
258
  "ringg-bot/tests/test_custom_vars_pipeline_parity.py",
259
+ "ringg-bot/tests/test_llm_shadow.py",
243
260
  "ringg-bot/simulation/simulation_service.py",
244
261
  "ringg-bot/simulation/__init__.py",
245
262
  "ringg-bot/simulation/router.py",
@@ -250,9 +267,11 @@
250
267
  "ringg-bot/utils/deepgram_warmup.py",
251
268
  "ringg-bot/utils/collect_prompts.py",
252
269
  "ringg-bot/utils/tools_helper.py",
270
+ "ringg-bot/utils/llm_shadow.py",
253
271
  "ringg-bot/utils/backend_url_resolver.py",
254
272
  "ringg-bot/utils/test_cache_8k.mp3",
255
273
  "ringg-bot/utils/language_resolver.py",
274
+ "ringg-bot/utils/media_mode.py",
256
275
  "ringg-bot/utils/tracking_helpers.py",
257
276
  "ringg-bot/utils/transcript.py",
258
277
  "ringg-bot/utils/logger_config.py",
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.0.21",
3
+ "distance": 1680,
4
+ "node": "g38f3578bb39abc4fd2a9e8387cf026bf4e7055e2",
5
+ "dirty": false,
6
+ "branch": "feat/widget-action-router",
7
+ "node_date": "2026-07-20"
8
+ }
@@ -71,6 +71,42 @@ ONGOING_ACTIONS_WAIT_TIMEOUT_S = 60.0
71
71
  _EXTRACT_VAR_RE = re.compile(
72
72
  r"@?\[\{\s*([A-Za-z_][A-Za-z0-9_.]*)\s*\}\]"
73
73
  )
74
+ # Whole string is exactly ONE token (``[{x}]`` / ``@[{x}]``, no surrounding
75
+ # text). Callers that want to preserve a resolved list/dict value rather than
76
+ # stringify it (e.g. chaining a JSON array into a later webhook body) match on
77
+ # this and use ``resolve_extract_path`` directly.
78
+ _EXACT_EXTRACT_RE = re.compile(
79
+ r"^@?\[\{\s*([A-Za-z_][A-Za-z0-9_.]*)\s*\}\]$"
80
+ )
81
+
82
+
83
+ def resolve_extract_path(state: dict, key: str):
84
+ """Walk a dotted ``a.b.0.c`` path through nested dicts/lists in ``state``.
85
+
86
+ Numeric segments index lists (an Action node's stored response is often a
87
+ root array). Returns ``None`` on any missing/out-of-range segment.
88
+ """
89
+ cur = state
90
+ for seg in key.split("."):
91
+ if isinstance(cur, dict) and seg in cur:
92
+ cur = cur[seg]
93
+ elif isinstance(cur, list):
94
+ try:
95
+ cur = cur[int(seg)]
96
+ except (ValueError, IndexError, TypeError):
97
+ return None
98
+ else:
99
+ return None
100
+ return cur
101
+
102
+
103
+ def exact_extract_key(text: str):
104
+ """Return the inner dotted key iff ``text`` is exactly one extract token
105
+ (``[{a.b}]`` / ``@[{a.b}]``), else ``None``. Whitespace-tolerant."""
106
+ if not isinstance(text, str):
107
+ return None
108
+ match = _EXACT_EXTRACT_RE.match(text.strip())
109
+ return match.group(1) if match else None
74
110
 
75
111
 
76
112
  def substitute_extract_vars(text: str, state: dict) -> str:
@@ -79,18 +115,17 @@ def substitute_extract_vars(text: str, state: dict) -> str:
79
115
  Supports dotted paths (``[{user.name}]``). Unknown variables (key not
80
116
  in state, or any path segment missing) are LEFT LITERAL — the marker
81
117
  survives so downstream debugging can spot the misconfigured extract.
118
+
119
+ Always returns a STRING (values are ``str()``-ified) — this is the
120
+ TTS/text-interpolation path. Callers needing to preserve a resolved
121
+ list/dict (e.g. a JSON webhook body value that is exactly one token)
122
+ should special-case ``exact_extract_key`` + ``resolve_extract_path``.
82
123
  """
83
124
  if not text or "[{" not in text or not isinstance(state, dict):
84
125
  return text
85
126
 
86
127
  def _resolve(key: str):
87
- cur = state
88
- for seg in key.split("."):
89
- if isinstance(cur, dict) and seg in cur:
90
- cur = cur[seg]
91
- else:
92
- return None
93
- return cur
128
+ return resolve_extract_path(state, key)
94
129
 
95
130
  def _repl(match: "re.Match[str]") -> str:
96
131
  value = _resolve(match.group(1))
@@ -298,14 +298,23 @@ def pick_branch_target(
298
298
  # the legacy `_node` / `_max_unclear` names for historical reasons,
299
299
  # but those keys never reach this dict at runtime.
300
300
  max_attempts = int(node_config.get("router_max_attempts", 2))
301
- if attempts > max_attempts:
301
+ reprompt_message = node_config.get("router_reprompt_message")
302
+ unclear_next = node_config.get("router_unclear_next")
303
+ # The under-cap path needs somewhere to retry: an inline re-prompt or a
304
+ # dedicated unclear Speak node. With NEITHER configured (only
305
+ # exhausted_next set, as the router config UI allows) there is no retry
306
+ # to perform, so an unclear pick has nowhere to go while under the cap —
307
+ # returning unclear_next=None here dead-airs the whole call even though
308
+ # a valid exhausted target exists. Fall straight through to exhausted in
309
+ # that case instead of after an unreachable retry loop.
310
+ if attempts > max_attempts or (not reprompt_message and not unclear_next):
302
311
  attempt_state[node_id] = 0 # reset for next time we enter this node
303
312
  return node_config.get("router_exhausted_next")
304
313
  # Under cap: prefer the new inline re-prompt if configured, else fall
305
314
  # back to the legacy separate-Speak unclear target.
306
- if node_config.get("router_reprompt_message"):
315
+ if reprompt_message:
307
316
  return REPROMPT_IN_PLACE
308
- return node_config.get("router_unclear_next")
317
+ return unclear_next
309
318
 
310
319
  return None # caller treats unknown id as a parse failure
311
320
 
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "0.0.21",
3
- "distance": 1590,
4
- "node": "g68bb0481ac9d17412e39fb73da7a0e07253e200d",
5
- "dirty": true,
6
- "branch": "feat/livekit_transport",
7
- "node_date": "2026-07-06"
8
- }