flowjet 2.0.7__tar.gz → 2.0.9__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 (170) hide show
  1. {flowjet-2.0.7 → flowjet-2.0.9}/CHANGELOG.md +31 -0
  2. {flowjet-2.0.7 → flowjet-2.0.9}/PKG-INFO +1 -1
  3. {flowjet-2.0.7 → flowjet-2.0.9}/pyproject.toml +1 -1
  4. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/cli/cli.py +62 -1
  5. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/cli/stream.py +72 -3
  6. flowjet-2.0.9/src/flowjet/core/_warnings.py +53 -0
  7. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/backends/direct.py +1 -0
  8. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/bridges/nano/backend.py +1 -0
  9. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/bridges/nano/mapping.py +63 -4
  10. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/events.py +13 -0
  11. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/server/__main__.py +6 -0
  12. {flowjet-2.0.7 → flowjet-2.0.9}/.gitignore +0 -0
  13. {flowjet-2.0.7 → flowjet-2.0.9}/LICENSE +0 -0
  14. {flowjet-2.0.7 → flowjet-2.0.9}/README.md +0 -0
  15. {flowjet-2.0.7 → flowjet-2.0.9}/config/nano.yml +0 -0
  16. {flowjet-2.0.7 → flowjet-2.0.9}/deploy/README.md +0 -0
  17. {flowjet-2.0.7 → flowjet-2.0.9}/deploy/nano.yml +0 -0
  18. {flowjet-2.0.7 → flowjet-2.0.9}/docs/completion-design.md +0 -0
  19. {flowjet-2.0.7 → flowjet-2.0.9}/docs/drafts/README.md +0 -0
  20. {flowjet-2.0.7 → flowjet-2.0.9}/docs/drafts/api-spec.md +0 -0
  21. {flowjet-2.0.7 → flowjet-2.0.9}/docs/impl/IG-001-openai-compatible-server.md +0 -0
  22. {flowjet-2.0.7 → flowjet-2.0.9}/docs/impl/IG-002-isolated-thread-pool-runtime.md +0 -0
  23. {flowjet-2.0.7 → flowjet-2.0.9}/docs/impl/IG-003-production-isolation-hardening.md +0 -0
  24. {flowjet-2.0.7 → flowjet-2.0.9}/docs/impl/README.md +0 -0
  25. {flowjet-2.0.7 → flowjet-2.0.9}/docs/impl/TEST-COVERAGE-MATRIX.md +0 -0
  26. {flowjet-2.0.7 → flowjet-2.0.9}/docs/naming.md +0 -0
  27. {flowjet-2.0.7 → flowjet-2.0.9}/docs/server.md +0 -0
  28. {flowjet-2.0.7 → flowjet-2.0.9}/docs/specs/RFC-001-openai-compatible-api.md +0 -0
  29. {flowjet-2.0.7 → flowjet-2.0.9}/docs/specs/RFC-002-isolated-thread-pool-runtime.md +0 -0
  30. {flowjet-2.0.7 → flowjet-2.0.9}/docs/specs/RFC-003-production-isolation-hardening.md +0 -0
  31. {flowjet-2.0.7 → flowjet-2.0.9}/docs/specs/RFC-004-unified-package.md +0 -0
  32. {flowjet-2.0.7 → flowjet-2.0.9}/docs/specs/rfc-history.md +0 -0
  33. {flowjet-2.0.7 → flowjet-2.0.9}/docs/specs/rfc-index.md +0 -0
  34. {flowjet-2.0.7 → flowjet-2.0.9}/docs/specs/rfc-namings.md +0 -0
  35. {flowjet-2.0.7 → flowjet-2.0.9}/docs/specs/templates/architecture-design.md +0 -0
  36. {flowjet-2.0.7 → flowjet-2.0.9}/docs/specs/templates/conceptual-design.md +0 -0
  37. {flowjet-2.0.7 → flowjet-2.0.9}/docs/specs/templates/impl-interface-design.md +0 -0
  38. {flowjet-2.0.7 → flowjet-2.0.9}/docs/specs/templates/rfc-standard.md +0 -0
  39. {flowjet-2.0.7 → flowjet-2.0.9}/docs/specs/templates/rfc-template.md +0 -0
  40. {flowjet-2.0.7 → flowjet-2.0.9}/docs/upgrade.md +0 -0
  41. {flowjet-2.0.7 → flowjet-2.0.9}/examples/README.md +0 -0
  42. {flowjet-2.0.7 → flowjet-2.0.9}/nano.yml +0 -0
  43. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/__init__.py +0 -0
  44. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/__main__.py +0 -0
  45. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/README.md +0 -0
  46. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/gws-drive/SKILL.md +0 -0
  47. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/gws-drive-upload/SKILL.md +0 -0
  48. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/logo-generator-skill/.env.example +0 -0
  49. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/logo-generator-skill/README.md +0 -0
  50. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/logo-generator-skill/SKILL.md +0 -0
  51. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/logo-generator-skill/assets/background_library.html +0 -0
  52. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/logo-generator-skill/assets/showcase_template.html +0 -0
  53. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/logo-generator-skill/references/background_styles.md +0 -0
  54. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/logo-generator-skill/references/design_patterns.md +0 -0
  55. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/logo-generator-skill/references/webgl_backgrounds.md +0 -0
  56. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/logo-generator-skill/requirements.txt +0 -0
  57. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/logo-generator-skill/scripts/generate_showcase.py +0 -0
  58. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/logo-generator-skill/scripts/svg_to_png.py +0 -0
  59. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/SKILL.md +0 -0
  60. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/assets/brainstorming/spec-document-reviewer-prompt.md +0 -0
  61. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/assets/implementation/coding-plan-template.md +0 -0
  62. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/assets/implementation/impl-guide-template.md +0 -0
  63. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/assets/review/pr-review-template.md +0 -0
  64. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/assets/review/review-checklist.md +0 -0
  65. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/assets/specs/rfc-template.md +0 -0
  66. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-architecture-design.md +0 -0
  67. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-conceptual-design.md +0 -0
  68. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-impl-interface-design.md +0 -0
  69. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-drafts-readme.md +0 -0
  70. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-impl-readme.md +0 -0
  71. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-platonic.yml +0 -0
  72. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-history.md +0 -0
  73. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-index.md +0 -0
  74. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-namings.md +0 -0
  75. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-standard.md +0 -0
  76. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/BRAINSTORM/brainstorm.md +0 -0
  77. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-code.md +0 -0
  78. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-create-guide.md +0 -0
  79. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-full.md +0 -0
  80. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-update-guide.md +0 -0
  81. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-validate-guide.md +0 -0
  82. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-plan-modular-specs.md +0 -0
  83. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-architecture.md +0 -0
  84. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-conceptual.md +0 -0
  85. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-impl-interface.md +0 -0
  86. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-scaffold.md +0 -0
  87. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-scan.md +0 -0
  88. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/REFERENCE.md +0 -0
  89. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/REVIEW/review-spec-compliance.md +0 -0
  90. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-check-compliance.md +0 -0
  91. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-check-taxonomy.md +0 -0
  92. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-history.md +0 -0
  93. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-index.md +0 -0
  94. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-namings.md +0 -0
  95. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-refine.md +0 -0
  96. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-validate-consistency.md +0 -0
  97. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-overview.md +0 -0
  98. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-1.md +0 -0
  99. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-2.md +0 -0
  100. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-3.md +0 -0
  101. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/research-bootstrap/SKILL.md +0 -0
  102. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/research-bootstrap/patterns/evidence-deep.json +0 -0
  103. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/research-bootstrap/patterns/evidence-first.json +0 -0
  104. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/research-bootstrap/patterns/idea-first.json +0 -0
  105. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/research-bootstrap/patterns/loop.json +0 -0
  106. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/research-bootstrap/patterns/paper-pipeline.json +0 -0
  107. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/research-bootstrap/patterns/rapid.json +0 -0
  108. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/research-bootstrap/patterns/rigor-first.json +0 -0
  109. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/research-bootstrap/patterns/stance-first.json +0 -0
  110. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/research-bootstrap/patterns/systematic-review.json +0 -0
  111. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/research-bootstrap/patterns/verify-loop.json +0 -0
  112. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/research-bootstrap/references/backends.md +0 -0
  113. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/research-bootstrap/references/handoff-ars-omr.md +0 -0
  114. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/research-bootstrap/references/handoff-omr-ars.md +0 -0
  115. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/research-bootstrap/references/routing.md +0 -0
  116. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/research-bootstrap/scripts/csljson_to_omr.py +0 -0
  117. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/builtin_skills/research-bootstrap/scripts/papers_index_to_csljson.py +0 -0
  118. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/cli/__init__.py +0 -0
  119. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/cli/__main__.py +0 -0
  120. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/cli/agent.py +0 -0
  121. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/cli/completion/__init__.py +0 -0
  122. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/cli/completion/context.py +0 -0
  123. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/cli/completion/engine.py +0 -0
  124. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/cli/doctor_cmd.py +0 -0
  125. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/cli/doctor_fix_cmd.py +0 -0
  126. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/cli/progress.py +0 -0
  127. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/cli/serve_cmd.py +0 -0
  128. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/cli/setup_cmd.py +0 -0
  129. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/cli/shell/__init__.py +0 -0
  130. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/cli/shell/_fj.zsh +0 -0
  131. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/cli/shell/fj.bash +0 -0
  132. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/cli/threads.py +0 -0
  133. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/cli/tool_stream.py +0 -0
  134. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/__init__.py +0 -0
  135. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/backend.py +0 -0
  136. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/backends/__init__.py +0 -0
  137. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/backends/fake.py +0 -0
  138. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/backends/isolation/__init__.py +0 -0
  139. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/backends/isolation/adapter.py +0 -0
  140. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/backends/isolation/admission.py +0 -0
  141. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/backends/isolation/backend.py +0 -0
  142. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/backends/isolation/errors.py +0 -0
  143. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/backends/isolation/mode_gate.py +0 -0
  144. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/backends/isolation/pool.py +0 -0
  145. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/backends/isolation/request.py +0 -0
  146. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/backends/isolation/workspace.py +0 -0
  147. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/bootstrap.py +0 -0
  148. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/bridges/nano/__init__.py +0 -0
  149. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/bridges/nano/adapter.py +0 -0
  150. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/identity.py +0 -0
  151. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/modes.py +0 -0
  152. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/protocol.py +0 -0
  153. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/tool_results.py +0 -0
  154. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/core/tool_stream.py +0 -0
  155. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/home.py +0 -0
  156. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/server/__init__.py +0 -0
  157. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/server/acp/__init__.py +0 -0
  158. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/server/acp/agent.py +0 -0
  159. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/server/acp/routes.py +0 -0
  160. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/server/config.py +0 -0
  161. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/server/http/__init__.py +0 -0
  162. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/server/http/app.py +0 -0
  163. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/server/http/auth.py +0 -0
  164. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/server/openai_compat/__init__.py +0 -0
  165. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/server/openai_compat/errors.py +0 -0
  166. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/server/openai_compat/projection.py +0 -0
  167. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/server/openai_compat/routes.py +0 -0
  168. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/server/openai_compat/schemas.py +0 -0
  169. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/server/openai_compat/service.py +0 -0
  170. {flowjet-2.0.7 → flowjet-2.0.9}/src/flowjet/server/openai_compat/store.py +0 -0
@@ -10,6 +10,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [2.0.9] — 2026-09-24
14
+
15
+ ### Fixed
16
+
17
+ - **Suppress noisy third-party warnings at startup.** `requests` emits
18
+ `RequestsDependencyWarning` when the installed `urllib3` / `chardet` /
19
+ `charset_normalizer` versions fall outside its supported range, and
20
+ `soothe`'s `TypeSafeClassifier` triggers LangChain's `LangChainBetaWarning`.
21
+ Both are version-mismatch or beta-API notices the user cannot act on, so
22
+ they are now silenced at the earliest point of each entry point. A new
23
+ `core/_warnings.py` module exposes `suppress_known_warnings()`, called from
24
+ both the CLI (`cli.py`) and server (`server/__main__.py`) `main()` before
25
+ the heavy agent stack is imported.
26
+
27
+ ## [2.0.8] — 2026-09-24
28
+
29
+ ### Added
30
+
31
+ - **`ask_user` interrupt/resume in the CLI.** The nano runtime now surfaces
32
+ soothe's `ask_user` interrupt so the agent can pause mid-run to ask
33
+ structured questions and resume on a follow-up invocation. `RunRequest`
34
+ gains `resume_value`, which the direct and nano backends forward into
35
+ LangGraph's `Command(resume=...)` instead of starting a new turn.
36
+ `mapping.py` extracts the `ask_user` questions and interrupt ids from a
37
+ `__interrupt__` payload and carries them on `InterruptWaiting`; `stream.py`
38
+ renders the questions block (options, recommended marker, resume hint) and
39
+ returns an `INTERRUPTED` sentinel instead of "Done" when the graph pauses.
40
+ `cli.py` detects a pending `ask_user` interrupt on the thread snapshot,
41
+ prints the questions, and feeds the user's query text as the resume answer
42
+ — so `fjf -t <thread> <answer>` continues the paused run.
43
+
13
44
  ## [2.0.7] — 2026-09-23
14
45
 
15
46
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: flowjet
3
- Version: 2.0.7
3
+ Version: 2.0.9
4
4
  Summary: FlowJet — agent CLI (fj) and server (ACP + OpenAI-compatible HTTP) powered by soothe-nano
5
5
  Project-URL: Homepage, https://github.com/caesar0301/flowjet
6
6
  Project-URL: Repository, https://github.com/caesar0301/flowjet
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "flowjet"
7
- version = "2.0.7"
7
+ version = "2.0.9"
8
8
  description = "FlowJet — agent CLI (fj) and server (ACP + OpenAI-compatible HTTP) powered by soothe-nano"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -518,6 +518,37 @@ def run_pin_thread(thread_id: str) -> int:
518
518
  return 0
519
519
 
520
520
 
521
+ async def _detect_pending_interrupt(agent: Any, thread_id: str) -> Any:
522
+ """Return the pending ``ask_user`` interrupt value, or ``None``.
523
+
524
+ Checks the LangGraph snapshot for the thread. When an ``ask_user``
525
+ interrupt is pending, its ``value`` carries the question specs; the CLI
526
+ can resume the run by feeding the user's answer to ``Command(resume=...)``.
527
+ """
528
+ get_state = getattr(agent, "aget_state", None)
529
+ if not callable(get_state):
530
+ return None
531
+ try:
532
+ snapshot = await get_state({"configurable": {"thread_id": thread_id}})
533
+ except Exception:
534
+ return None
535
+ if snapshot is None:
536
+ return None
537
+ interrupts = getattr(snapshot, "interrupts", None) or ()
538
+ if not interrupts:
539
+ # Some snapshots nest interrupts under per-task state.
540
+ tasks = getattr(snapshot, "tasks", None) or ()
541
+ for task in tasks:
542
+ task_interrupts = getattr(task, "interrupts", None) or ()
543
+ interrupts = task_interrupts
544
+ break
545
+ for entry in interrupts:
546
+ value = getattr(entry, "value", None)
547
+ if isinstance(value, dict) and value.get("type") == "ask_user":
548
+ return value
549
+ return None
550
+
551
+
521
552
  async def run_async(args: argparse.Namespace) -> int:
522
553
  conflict = validate_arg_composition(args)
523
554
  if conflict:
@@ -570,14 +601,39 @@ async def run_async(args: argparse.Namespace) -> int:
570
601
  ask_mode=getattr(args, "ask", False),
571
602
  bypass_mode=getattr(args, "bypass", False),
572
603
  )
604
+ # If the thread paused on an ``ask_user`` interrupt, the user's
605
+ # query text is their answer — resume the run instead of
606
+ # starting a new turn. ``fjf`` (follow) hits this path when the
607
+ # previous run ended on a question.
608
+ resume_value: Any = None
609
+ pending = await _detect_pending_interrupt(agent, thread_id)
610
+ if pending is not None:
611
+ from flowjet.cli.stream import format_questions_block
612
+
613
+ questions = pending.get("questions") or ()
614
+ if isinstance(questions, list):
615
+ questions = tuple(q for q in questions if isinstance(q, dict))
616
+ else:
617
+ questions = ()
618
+ # Print the questions so the user sees what they're answering.
619
+ if questions:
620
+ sys.stdout.write(format_questions_block(questions, thread_id))
621
+ sys.stdout.flush()
622
+ resume_value = {"answers": [args.query_text]}
573
623
  if args.no_stream:
574
- await invoke_query(agent, args.query_text, thread_id=thread_id)
624
+ await invoke_query(
625
+ agent,
626
+ args.query_text,
627
+ thread_id=thread_id,
628
+ resume_value=resume_value,
629
+ )
575
630
  else:
576
631
  await stream_query(
577
632
  agent,
578
633
  args.query_text,
579
634
  thread_id=thread_id,
580
635
  show_tool_calls=args.verbose,
636
+ resume_value=resume_value,
581
637
  )
582
638
  except ConcurrentSessionError as exc:
583
639
  sys.stderr.write(f"error: {exc}\n")
@@ -661,6 +717,11 @@ def main_follow(argv: list[str] | None = None) -> int:
661
717
 
662
718
 
663
719
  def main(argv: list[str] | None = None) -> int:
720
+ # Silence noisy third-party warnings (requests version-mismatch,
721
+ # LangChain beta-API) before any heavy import pulls them in.
722
+ from flowjet.core._warnings import suppress_known_warnings
723
+
724
+ suppress_known_warnings()
664
725
  try:
665
726
  # Parse first so ``-V`` / ``-h`` / ``--help`` exit before we touch the
666
727
  # logging stack (and the heavy ``flowjet.cli.agent`` import it used to
@@ -36,6 +36,11 @@ from flowjet.core.tool_results import (
36
36
  # Min interval between live narration previews on the progress line (seconds).
37
37
  _STATUS_PREVIEW_MIN_INTERVAL = 0.12
38
38
 
39
+ # Returned by ``stream_query`` / ``invoke_query`` when the agent paused for
40
+ # human input (``ask_user`` interrupt) instead of completing. The CLI checks
41
+ # for this sentinel to print the resume hint and accept an answer via ``fjf``.
42
+ INTERRUPTED = "__FJ_INTERRUPTED__"
43
+
39
44
 
40
45
  def _format_duration(seconds: float) -> str:
41
46
  """Format an elapsed time as a short human-readable duration.
@@ -273,6 +278,44 @@ class AnswerWriter:
273
278
  return self.buf
274
279
 
275
280
 
281
+ def format_questions_block(questions: tuple[dict[str, Any], ...], thread_id: str) -> str:
282
+ """Render ``ask_user`` questions as a terminal block with a resume hint.
283
+
284
+ The block is written to stdout after the ephemeral progress line is
285
+ cleared, so it stays visible. Each question lists its options with the
286
+ recommended one (label ending in "(Recommended)") marked. The footer
287
+ tells the user how to resume with an answer via ``fjf``.
288
+ """
289
+ if not questions:
290
+ return f"\n⏸ Paused for input · resume with: fjf -t {thread_id} <your answer>\n"
291
+ lines: list[str] = ["", "⏸ Paused for input:"]
292
+ for qi, q in enumerate(questions, 1):
293
+ header = str(q.get("header") or "").strip()
294
+ question = str(q.get("question") or "").strip()
295
+ if len(questions) > 1:
296
+ prefix = f" Q{qi}: "
297
+ else:
298
+ prefix = " "
299
+ if header:
300
+ lines.append(f"{prefix}[{header}] {question}")
301
+ else:
302
+ lines.append(f"{prefix}{question}")
303
+ options = q.get("options")
304
+ if isinstance(options, list):
305
+ for opt in options:
306
+ if not isinstance(opt, dict):
307
+ continue
308
+ label = str(opt.get("label") or "").strip()
309
+ desc = str(opt.get("description") or "").strip()
310
+ marker = " ★" if "(Recommended)" in label or "Recommended" in label else ""
311
+ if desc:
312
+ lines.append(f" • {label}{marker} — {desc}")
313
+ else:
314
+ lines.append(f" • {label}{marker}")
315
+ lines.append(f"\n Resume with: fjf -t {thread_id} <your answer>\n")
316
+ return "\n".join(lines) + "\n"
317
+
318
+
276
319
  async def stream_query(
277
320
  agent: SootheNanoAgent,
278
321
  query: str,
@@ -283,8 +326,14 @@ async def stream_query(
283
326
  out: TextIO | None = None,
284
327
  err: TextIO | None = None,
285
328
  progress: ProgressLine | None = None,
329
+ resume_value: Any = None,
286
330
  ) -> str:
287
- """Run a query with ephemeral progress; print the complete final answer."""
331
+ """Run a query with ephemeral progress; print the complete final answer.
332
+
333
+ When the agent pauses for human input (``ask_user`` interrupt), the
334
+ questions are rendered and :data:`INTERRUPTED` is returned — the run is
335
+ not complete and must be resumed with ``fjf -t <thread> <answer>``.
336
+ """
288
337
  stdout = out or sys.stdout
289
338
  stderr = err or sys.stderr
290
339
  status = progress if progress is not None else ProgressLine(stdout)
@@ -293,6 +342,7 @@ async def stream_query(
293
342
  # ``-v``: emit each tool call once when args are available.
294
343
  mirrored_tool_ids: set[str] = set()
295
344
  started_at = time.monotonic()
345
+ interrupted = False
296
346
 
297
347
  # Deepagents emits a deprecation warning mid-run that would smash the
298
348
  # ephemeral progress line when mixed onto the terminal.
@@ -309,7 +359,12 @@ async def stream_query(
309
359
  async with status:
310
360
  status.update("Thinking", color="cyan")
311
361
  async for event in backend.stream_run(
312
- RunRequest(model="default", input_text=query, session=thread_id)
362
+ RunRequest(
363
+ model="default",
364
+ input_text=query,
365
+ session=thread_id,
366
+ resume_value=resume_value,
367
+ )
313
368
  ):
314
369
  if isinstance(event, Progress):
315
370
  # ``raw`` carries the soothe-nano custom payload so the
@@ -378,7 +433,15 @@ async def stream_query(
378
433
 
379
434
  if isinstance(event, InterruptWaiting):
380
435
  status.update("Waiting for input…", color="yellow")
381
- if show_tool_calls:
436
+ if event.questions:
437
+ # A real ``ask_user`` interrupt pauses the graph —
438
+ # render the questions and mark the run as interrupted
439
+ # so the CLI returns ``INTERRUPTED`` instead of "Done".
440
+ status.blank()
441
+ stdout.write(format_questions_block(event.questions, thread_id))
442
+ stdout.flush()
443
+ interrupted = True
444
+ elif show_tool_calls:
382
445
  _write_verbose(
383
446
  status,
384
447
  stderr,
@@ -390,6 +453,10 @@ async def stream_query(
390
453
  raise RuntimeError(event.message)
391
454
 
392
455
  answer_text = answer.finish()
456
+ if interrupted:
457
+ # The run paused for input — don't print "Done", the questions block
458
+ # (or generic pause message) is already on stdout.
459
+ return INTERRUPTED
393
460
  duration = _format_duration(time.monotonic() - started_at)
394
461
  stdout.write(f"\n✓ Done · {duration} · {thread_id}\n")
395
462
  stdout.flush()
@@ -403,6 +470,7 @@ async def invoke_query(
403
470
  thread_id: str,
404
471
  out: TextIO | None = None,
405
472
  progress: ProgressLine | None = None,
473
+ resume_value: Any = None,
406
474
  ) -> str:
407
475
  """Progress until done, then print the final text (no live narration preview)."""
408
476
  return await stream_query(
@@ -413,4 +481,5 @@ async def invoke_query(
413
481
  live_answer=False,
414
482
  out=out,
415
483
  progress=progress,
484
+ resume_value=resume_value,
416
485
  )
@@ -0,0 +1,53 @@
1
+ """Suppress noisy third-party warnings that are not actionable for users.
2
+
3
+ These warnings come from dependencies (``requests``, ``soothe``/``langchain``)
4
+ and fire at import time or agent build time. They are version-mismatch or
5
+ beta-API notices that the user cannot fix, so we silence them globally at the
6
+ earliest point of each entry point (CLI ``main`` and server ``main``).
7
+
8
+ Call :func:`suppress_known_warnings` before importing the heavy agent stack.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import warnings
14
+
15
+
16
+ def suppress_known_warnings() -> None:
17
+ """Silence dependency warnings that users cannot act on.
18
+
19
+ Safe to call multiple times — ``filterwarnings`` is idempotent for the
20
+ same rule.
21
+
22
+ Suppressed:
23
+
24
+ * ``RequestsDependencyWarning`` — ``requests`` emits this when the
25
+ installed ``urllib3`` / ``chardet`` / ``charset_normalizer`` versions
26
+ fall outside its supported range. The versions still work; the warning
27
+ is informational only.
28
+ * ``LangChainBetaWarning`` — ``soothe`` uses ``TypeSafeClassifier`` which
29
+ is a beta LangChain API. The API may change, but the warning is noise
30
+ for end users.
31
+ """
32
+ # ``requests`` import-time version-mismatch warning.
33
+ warnings.filterwarnings(
34
+ "ignore",
35
+ category=UserWarning,
36
+ message=r".*urllib3 .* or chardet .* doesn't match a supported version.*",
37
+ )
38
+ # Also match by the concrete warning class when available — covers the
39
+ # exact ``RequestsDependencyWarning`` emitted by ``requests/__init__.py``.
40
+ try:
41
+ from requests.exceptions import RequestsDependencyWarning
42
+
43
+ warnings.filterwarnings("ignore", category=RequestsDependencyWarning)
44
+ except Exception: # pragma: no cover - requests not installed / API changed
45
+ pass
46
+
47
+ # ``soothe`` / LangChain beta-API warning from ``TypeSafeClassifier``.
48
+ try:
49
+ from langchain_core._api.beta_decorator import LangChainBetaWarning
50
+
51
+ warnings.filterwarnings("ignore", category=LangChainBetaWarning)
52
+ except Exception: # pragma: no cover - langchain not installed / API changed
53
+ pass
@@ -55,5 +55,6 @@ class DirectRuntimeBackend:
55
55
  # The CLI resolves the interaction mode when it builds the agent
56
56
  # (auto / ask / bypass); nano must not be pinned back to "agent".
57
57
  interaction_mode=None,
58
+ resume_value=request.resume_value,
58
59
  ):
59
60
  yield event
@@ -57,6 +57,7 @@ class NanoRuntimeBackend:
57
57
  workspace=workspace,
58
58
  thread_id=session,
59
59
  interaction_mode=resolve_interaction_mode(request.metadata),
60
+ resume_value=request.resume_value,
60
61
  ):
61
62
  yield event
62
63
 
@@ -298,6 +298,46 @@ def _wire_tool_call_update(
298
298
  return [ToolStarted(tool=name or "tool", call_id=tc_id, args=args or None)]
299
299
 
300
300
 
301
+ def _extract_ask_user_interrupts(
302
+ raw: Any,
303
+ ) -> tuple[tuple[dict[str, Any], ...], tuple[str, ...]]:
304
+ """Pull ``ask_user`` questions and interrupt ids from a ``__interrupt__`` payload.
305
+
306
+ LangGraph emits ``__interrupt__`` as a tuple of ``Interrupt`` objects (or
307
+ dicts). Each carries a ``value`` with the interrupt payload; soothe's
308
+ ``ask_user`` tool tags it with ``{"type": "ask_user", "questions": [...]}``.
309
+
310
+ Returns ``(questions, interrupt_ids)`` — only questions from ``ask_user``
311
+ interrupts are surfaced; other interrupt kinds (tool approval, etc.) yield
312
+ an empty questions tuple but still carry their interrupt id so the CLI can
313
+ render a generic "paused for input" message.
314
+ """
315
+ if not isinstance(raw, (list, tuple)):
316
+ raw = (raw,) if raw is not None else ()
317
+ questions: list[dict[str, Any]] = []
318
+ interrupt_ids: list[str] = []
319
+ for entry in raw:
320
+ value: Any = None
321
+ int_id: str = ""
322
+ # LangGraph Interrupt objects have ``.value`` / ``.id``; serialized
323
+ # dicts use the same keys.
324
+ if hasattr(entry, "value"):
325
+ value = getattr(entry, "value", None)
326
+ int_id = str(getattr(entry, "id", "") or "")
327
+ elif isinstance(entry, dict):
328
+ value = entry.get("value")
329
+ int_id = str(entry.get("id", "") or "")
330
+ if int_id:
331
+ interrupt_ids.append(int_id)
332
+ if isinstance(value, dict) and value.get("type") == "ask_user":
333
+ qs = value.get("questions")
334
+ if isinstance(qs, list):
335
+ for q in qs:
336
+ if isinstance(q, dict):
337
+ questions.append(q)
338
+ return tuple(questions), tuple(interrupt_ids)
339
+
340
+
301
341
  async def iter_nano_runtime_events(
302
342
  agent: Any,
303
343
  *,
@@ -308,8 +348,14 @@ async def iter_nano_runtime_events(
308
348
  workspace: str | None = None,
309
349
  thread_id: str | None = None,
310
350
  interaction_mode: str | None = "agent",
351
+ resume_value: Any = None,
311
352
  ) -> AsyncIterator[RuntimeEvent]:
312
- """Drive ``agent.astream`` and yield sanitized runtime events."""
353
+ """Drive ``agent.astream`` and yield sanitized runtime events.
354
+
355
+ When ``resume_value`` is set, the run resumes a paused graph (an
356
+ ``ask_user`` interrupt) with that value via ``Command(resume=...)``
357
+ instead of starting a new turn from ``input_text``.
358
+ """
313
359
  try:
314
360
  from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
315
361
  except ImportError as exc: # pragma: no cover
@@ -327,7 +373,15 @@ async def iter_nano_runtime_events(
327
373
  if workspace:
328
374
  configurable["workspace"] = workspace
329
375
 
330
- messages = [HumanMessage(content=input_text)]
376
+ # Resume a paused graph (ask_user interrupt) rather than starting a new
377
+ # turn. LangGraph's ``Command(resume=...)`` delivers the value to the
378
+ # ``interrupt()`` call that suspended the graph.
379
+ if resume_value is not None:
380
+ from langgraph.types import Command
381
+
382
+ payload: Any = Command(resume=resume_value)
383
+ else:
384
+ payload = {"messages": [HumanMessage(content=input_text)]}
331
385
  config = {"configurable": configurable}
332
386
  answer = ""
333
387
  composing = False
@@ -340,7 +394,7 @@ async def iter_nano_runtime_events(
340
394
 
341
395
  try:
342
396
  async for chunk in agent.astream(
343
- {"messages": messages},
397
+ payload,
344
398
  config=config,
345
399
  stream_mode=["messages", "updates", "custom"],
346
400
  subgraphs=True,
@@ -369,7 +423,12 @@ async def iter_nano_runtime_events(
369
423
 
370
424
  if mode == "updates" and isinstance(data, dict):
371
425
  if "__interrupt__" in data:
372
- yield InterruptWaiting(message="Waiting for input…")
426
+ questions, interrupt_ids = _extract_ask_user_interrupts(data["__interrupt__"])
427
+ yield InterruptWaiting(
428
+ message="Waiting for input…",
429
+ questions=questions,
430
+ interrupt_ids=interrupt_ids,
431
+ )
373
432
  continue
374
433
  # Plan steps (todos) and host middleware phases are the only
375
434
  # signal soothe gives us about *what* it is doing between tool
@@ -63,7 +63,16 @@ class OutputTextDelta:
63
63
 
64
64
  @dataclass(frozen=True, slots=True)
65
65
  class InterruptWaiting:
66
+ """Agent paused for human input (``ask_user`` interrupt).
67
+
68
+ When the pause comes from soothe's ``ask_user`` tool, ``questions`` carries
69
+ the structured question specs the model asked, so the CLI can render them
70
+ and accept an answer that resumes the run on a follow-up invocation.
71
+ """
72
+
66
73
  message: str | None = None
74
+ questions: tuple[dict[str, Any], ...] = ()
75
+ interrupt_ids: tuple[str, ...] = ()
67
76
 
68
77
 
69
78
  @dataclass(frozen=True, slots=True)
@@ -92,6 +101,10 @@ class RunRequest:
92
101
  session: str | None = None
93
102
  metadata: dict[str, Any] | None = None
94
103
  run_id: str | None = None
104
+ # When set, the run resumes a paused graph (``ask_user`` interrupt) with the
105
+ # given value instead of starting a new turn from ``input_text``. The value
106
+ # is passed to LangGraph's ``Command(resume=...)``.
107
+ resume_value: Any = None
95
108
 
96
109
 
97
110
  @dataclass(frozen=True, slots=True)
@@ -35,6 +35,12 @@ def _create_dual_stack_socket(host: str, port: int) -> socket.socket:
35
35
 
36
36
 
37
37
  def main() -> None:
38
+ # Silence noisy third-party warnings (requests version-mismatch,
39
+ # LangChain beta-API) before the agent stack is imported.
40
+ from flowjet.core._warnings import suppress_known_warnings
41
+
42
+ suppress_known_warnings()
43
+
38
44
  # Pull in a ~/.soothe/config copy before the first agent build. Kept out of
39
45
  # create_app(): the ASGI app is built at import time, and importing a module
40
46
  # must not write files.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes