flowjet 2.0.4__tar.gz → 2.0.6__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.

Potentially problematic release.


This version of flowjet might be problematic. Click here for more details.

Files changed (170) hide show
  1. {flowjet-2.0.4 → flowjet-2.0.6}/CHANGELOG.md +42 -3
  2. {flowjet-2.0.4 → flowjet-2.0.6}/PKG-INFO +4 -4
  3. {flowjet-2.0.4 → flowjet-2.0.6}/README.md +1 -1
  4. {flowjet-2.0.4 → flowjet-2.0.6}/docs/impl/IG-002-isolated-thread-pool-runtime.md +2 -2
  5. {flowjet-2.0.4 → flowjet-2.0.6}/docs/server.md +2 -2
  6. {flowjet-2.0.4 → flowjet-2.0.6}/examples/README.md +2 -1
  7. {flowjet-2.0.4 → flowjet-2.0.6}/pyproject.toml +3 -3
  8. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/cli.py +2 -33
  9. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/completion/engine.py +2 -1
  10. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/doctor_cmd.py +1 -1
  11. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/setup_cmd.py +76 -17
  12. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/bootstrap.py +14 -5
  13. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/bridges/nano/mapping.py +78 -0
  14. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/tool_stream.py +29 -0
  15. flowjet-2.0.6/src/flowjet/home.py +130 -0
  16. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/__main__.py +5 -7
  17. flowjet-2.0.4/src/flowjet/core/home.py +0 -127
  18. {flowjet-2.0.4 → flowjet-2.0.6}/.gitignore +0 -0
  19. {flowjet-2.0.4 → flowjet-2.0.6}/LICENSE +0 -0
  20. {flowjet-2.0.4 → flowjet-2.0.6}/config/nano.yml +0 -0
  21. {flowjet-2.0.4 → flowjet-2.0.6}/deploy/README.md +0 -0
  22. {flowjet-2.0.4 → flowjet-2.0.6}/deploy/nano.yml +0 -0
  23. {flowjet-2.0.4 → flowjet-2.0.6}/docs/completion-design.md +0 -0
  24. {flowjet-2.0.4 → flowjet-2.0.6}/docs/drafts/README.md +0 -0
  25. {flowjet-2.0.4 → flowjet-2.0.6}/docs/drafts/api-spec.md +0 -0
  26. {flowjet-2.0.4 → flowjet-2.0.6}/docs/impl/IG-001-openai-compatible-server.md +0 -0
  27. {flowjet-2.0.4 → flowjet-2.0.6}/docs/impl/IG-003-production-isolation-hardening.md +0 -0
  28. {flowjet-2.0.4 → flowjet-2.0.6}/docs/impl/README.md +0 -0
  29. {flowjet-2.0.4 → flowjet-2.0.6}/docs/impl/TEST-COVERAGE-MATRIX.md +0 -0
  30. {flowjet-2.0.4 → flowjet-2.0.6}/docs/naming.md +0 -0
  31. {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/RFC-001-openai-compatible-api.md +0 -0
  32. {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/RFC-002-isolated-thread-pool-runtime.md +0 -0
  33. {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/RFC-003-production-isolation-hardening.md +0 -0
  34. {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/RFC-004-unified-package.md +0 -0
  35. {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/rfc-history.md +0 -0
  36. {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/rfc-index.md +0 -0
  37. {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/rfc-namings.md +0 -0
  38. {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/templates/architecture-design.md +0 -0
  39. {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/templates/conceptual-design.md +0 -0
  40. {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/templates/impl-interface-design.md +0 -0
  41. {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/templates/rfc-standard.md +0 -0
  42. {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/templates/rfc-template.md +0 -0
  43. {flowjet-2.0.4 → flowjet-2.0.6}/docs/upgrade.md +0 -0
  44. {flowjet-2.0.4 → flowjet-2.0.6}/nano.yml +0 -0
  45. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/__init__.py +0 -0
  46. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/__main__.py +0 -0
  47. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/README.md +0 -0
  48. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/gws-drive/SKILL.md +0 -0
  49. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/gws-drive-upload/SKILL.md +0 -0
  50. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/logo-generator-skill/.env.example +0 -0
  51. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/logo-generator-skill/README.md +0 -0
  52. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/logo-generator-skill/SKILL.md +0 -0
  53. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/logo-generator-skill/assets/background_library.html +0 -0
  54. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/logo-generator-skill/assets/showcase_template.html +0 -0
  55. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/logo-generator-skill/references/background_styles.md +0 -0
  56. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/logo-generator-skill/references/design_patterns.md +0 -0
  57. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/logo-generator-skill/references/webgl_backgrounds.md +0 -0
  58. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/logo-generator-skill/requirements.txt +0 -0
  59. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/logo-generator-skill/scripts/generate_showcase.py +0 -0
  60. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/logo-generator-skill/scripts/svg_to_png.py +0 -0
  61. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/SKILL.md +0 -0
  62. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/brainstorming/spec-document-reviewer-prompt.md +0 -0
  63. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/implementation/coding-plan-template.md +0 -0
  64. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/implementation/impl-guide-template.md +0 -0
  65. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/review/pr-review-template.md +0 -0
  66. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/review/review-checklist.md +0 -0
  67. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/specs/rfc-template.md +0 -0
  68. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-architecture-design.md +0 -0
  69. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-conceptual-design.md +0 -0
  70. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-impl-interface-design.md +0 -0
  71. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-drafts-readme.md +0 -0
  72. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-impl-readme.md +0 -0
  73. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-platonic.yml +0 -0
  74. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-history.md +0 -0
  75. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-index.md +0 -0
  76. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-namings.md +0 -0
  77. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-standard.md +0 -0
  78. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/BRAINSTORM/brainstorm.md +0 -0
  79. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-code.md +0 -0
  80. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-create-guide.md +0 -0
  81. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-full.md +0 -0
  82. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-update-guide.md +0 -0
  83. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-validate-guide.md +0 -0
  84. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-plan-modular-specs.md +0 -0
  85. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-architecture.md +0 -0
  86. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-conceptual.md +0 -0
  87. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-impl-interface.md +0 -0
  88. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-scaffold.md +0 -0
  89. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-scan.md +0 -0
  90. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/REFERENCE.md +0 -0
  91. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/REVIEW/review-spec-compliance.md +0 -0
  92. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-check-compliance.md +0 -0
  93. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-check-taxonomy.md +0 -0
  94. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-history.md +0 -0
  95. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-index.md +0 -0
  96. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-namings.md +0 -0
  97. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-refine.md +0 -0
  98. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-validate-consistency.md +0 -0
  99. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-overview.md +0 -0
  100. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-1.md +0 -0
  101. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-2.md +0 -0
  102. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-3.md +0 -0
  103. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/SKILL.md +0 -0
  104. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/evidence-deep.json +0 -0
  105. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/evidence-first.json +0 -0
  106. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/idea-first.json +0 -0
  107. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/loop.json +0 -0
  108. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/paper-pipeline.json +0 -0
  109. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/rapid.json +0 -0
  110. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/rigor-first.json +0 -0
  111. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/stance-first.json +0 -0
  112. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/systematic-review.json +0 -0
  113. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/verify-loop.json +0 -0
  114. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/references/backends.md +0 -0
  115. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/references/handoff-ars-omr.md +0 -0
  116. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/references/handoff-omr-ars.md +0 -0
  117. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/references/routing.md +0 -0
  118. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/scripts/csljson_to_omr.py +0 -0
  119. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/scripts/papers_index_to_csljson.py +0 -0
  120. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/__init__.py +0 -0
  121. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/__main__.py +0 -0
  122. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/agent.py +0 -0
  123. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/completion/__init__.py +0 -0
  124. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/completion/context.py +0 -0
  125. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/doctor_fix_cmd.py +0 -0
  126. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/progress.py +0 -0
  127. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/serve_cmd.py +0 -0
  128. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/shell/__init__.py +0 -0
  129. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/shell/_fj.zsh +0 -0
  130. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/shell/fj.bash +0 -0
  131. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/stream.py +0 -0
  132. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/threads.py +0 -0
  133. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/tool_stream.py +0 -0
  134. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/__init__.py +0 -0
  135. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backend.py +0 -0
  136. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/__init__.py +0 -0
  137. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/direct.py +0 -0
  138. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/fake.py +0 -0
  139. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/isolation/__init__.py +0 -0
  140. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/isolation/adapter.py +0 -0
  141. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/isolation/admission.py +0 -0
  142. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/isolation/backend.py +0 -0
  143. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/isolation/errors.py +0 -0
  144. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/isolation/mode_gate.py +0 -0
  145. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/isolation/pool.py +0 -0
  146. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/isolation/request.py +0 -0
  147. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/isolation/workspace.py +0 -0
  148. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/bridges/nano/__init__.py +0 -0
  149. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/bridges/nano/adapter.py +0 -0
  150. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/bridges/nano/backend.py +0 -0
  151. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/events.py +0 -0
  152. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/identity.py +0 -0
  153. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/modes.py +0 -0
  154. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/protocol.py +0 -0
  155. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/tool_results.py +0 -0
  156. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/__init__.py +0 -0
  157. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/acp/__init__.py +0 -0
  158. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/acp/agent.py +0 -0
  159. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/acp/routes.py +0 -0
  160. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/config.py +0 -0
  161. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/http/__init__.py +0 -0
  162. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/http/app.py +0 -0
  163. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/http/auth.py +0 -0
  164. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/openai_compat/__init__.py +0 -0
  165. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/openai_compat/errors.py +0 -0
  166. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/openai_compat/projection.py +0 -0
  167. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/openai_compat/routes.py +0 -0
  168. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/openai_compat/schemas.py +0 -0
  169. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/openai_compat/service.py +0 -0
  170. {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/openai_compat/store.py +0 -0
@@ -10,12 +10,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
- ## [2.0.4] — 2026-09-19
13
+ ## [2.0.6] — 2026-09-21
14
+
15
+ ### Changed
16
+
17
+ - **Bump soothe runtime to the latest.** `soothe` `1.0.12 → 1.0.14` and
18
+ `soothe-nano` `1.2.28 → 1.3.0`. The `<1.3.0` cap on `soothe-nano` was lifted
19
+ to `<1.4.0` because `soothe 1.0.14` now requires `soothe-nano>=1.3.0`. The
20
+ upgrade adds one transitive dependency, `langchain-typesafe`. All FlowJet
21
+ import paths into soothe/soothe-nano (`SootheConfig`, `create_nano_agent`,
22
+ `create_dual_mode_nano_agent`, `create_soothe_agent`, `resolve_checkpointer`,
23
+ `register_builtin_skill_root`, `FrameworkFilesystem`, `diagnose`, etc.) are
24
+ unchanged and continue to resolve.
25
+
26
+ ## [2.0.5] — 2026-09-20
14
27
 
15
28
  ### Changed
16
29
 
17
- - Bumped `soothe` lower bound to `1.0.13` (was `1.0.12`). soothe-nano stays
18
- pinned at `>=1.2.28,<1.3.0`.
30
+ - **Config moved off `~/.soothe` — `FLOWJET_HOME` is now the authority.**
31
+ The config file resolves to `$FLOWJET_HOME/config/nano.yml` (default
32
+ `~/.flowjet/config/nano.yml`) instead of `$SOOTHE_HOME/config/nano.yml`, so an
33
+ exported `SOOTHE_HOME` can no longer drag flowjet back onto `~/.soothe`.
34
+ `SOOTHE_HOME` itself is left untouched: CLI data (thread pins, locks,
35
+ completion history), logs, agents, plugins and the sqlite databases still live
36
+ there. Upgrades keep working — an existing `~/.soothe/config/nano.yml` (plus a
37
+ sibling `soothe.yml`) is copied into `$FLOWJET_HOME/config/` **once**, on first
38
+ run, and the originals are left in place, so the move is reversible.
39
+ - **`fj setup` no longer aborts when the LLM endpoint is unreachable.** A failed
40
+ or empty `/models` probe (connection refused, HTTP error, unresolved
41
+ `${ENV_VAR}`) now prints a `warning:` and falls through to manual model entry
42
+ — offering the models already saved for the selected provider, or accepting a
43
+ typed model id. The provider and router profile are still written, so a config
44
+ can be initialised before the model server is running; the run ends with a note
45
+ to re-check via `fj doctor`.
46
+
47
+ ## [2.0.4] — 2026-09-19
48
+
49
+ ### Fixed
50
+
51
+ - **`fj -V` is instant (1.78s → 0.06s).** It paid the full `soothe_nano` +
52
+ `flowjet.core.bootstrap` import cost on every invocation just to print a
53
+ version string. The stdlib-only logging helpers moved out of `agent.py` into
54
+ `cli.py`, `main()` parses args before setting up logging so `-V`/`-h` exit
55
+ first, and the `-c/--config` help text now resolves lazily.
56
+ (`agent.py` keeps thin delegation wrappers, so existing imports and
57
+ monkeypatching are unaffected.)
19
58
 
20
59
  ## [2.0.3] — 2026-09-18
21
60
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: flowjet
3
- Version: 2.0.4
3
+ Version: 2.0.6
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
@@ -22,8 +22,8 @@ Classifier: Programming Language :: Python :: 3.14
22
22
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
23
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
24
  Requires-Python: <3.15,>=3.11
25
- Requires-Dist: soothe-nano<1.3.0,>=1.2.28
26
- Requires-Dist: soothe<2.0.0,>=1.0.13
25
+ Requires-Dist: soothe-nano<1.4.0,>=1.3.0
26
+ Requires-Dist: soothe<2.0.0,>=1.0.14
27
27
  Provides-Extra: dev
28
28
  Requires-Dist: httpx>=0.27.0; extra == 'dev'
29
29
  Requires-Dist: mypy>=1.0.0; extra == 'dev'
@@ -75,7 +75,7 @@ export OPENAI_API_KEY=sk-...
75
75
  fj summarize README.md
76
76
  ```
77
77
 
78
- `fj setup` writes `~/.soothe/config/nano.yml`. Without it, FlowJet falls back to `OPENAI_API_KEY` or `ANTHROPIC_API_KEY`.
78
+ `fj setup` writes `$FLOWJET_HOME/config/nano.yml` (default `~/.flowjet/config/nano.yml`). Without it, FlowJet falls back to `OPENAI_API_KEY` or `ANTHROPIC_API_KEY`.
79
79
 
80
80
  Not sure your machine is ready? `fj doctor` (add `--deep`, `--live-llm`).
81
81
 
@@ -31,7 +31,7 @@ export OPENAI_API_KEY=sk-...
31
31
  fj summarize README.md
32
32
  ```
33
33
 
34
- `fj setup` writes `~/.soothe/config/nano.yml`. Without it, FlowJet falls back to `OPENAI_API_KEY` or `ANTHROPIC_API_KEY`.
34
+ `fj setup` writes `$FLOWJET_HOME/config/nano.yml` (default `~/.flowjet/config/nano.yml`). Without it, FlowJet falls back to `OPENAI_API_KEY` or `ANTHROPIC_API_KEY`.
35
35
 
36
36
  Not sure your machine is ready? `fj doctor` (add `--deep`, `--live-llm`).
37
37
 
@@ -193,8 +193,8 @@ graph; abnormal turns recycle it before the worker is made available.
193
193
 
194
194
  | Env | Settings field | Default |
195
195
  |-----|----------------|---------|
196
- | `FLOWJET_HOME` | `home` | `~/.flowjet` |
197
- | `FLOWJET_NANO_CONFIG` | `nano_config` | unset → `$SOOTHE_HOME/config/nano.yml` |
196
+ | `FLOWJET_HOME` | `home` | `~/.flowjet` (config file + workspaces) |
197
+ | `FLOWJET_NANO_CONFIG` | `nano_config` | unset → `$FLOWJET_HOME/config/nano.yml` |
198
198
  | `FLOWJET_THREAD_POOL_MIN` | `thread_pool_min` | `2` |
199
199
  | `FLOWJET_THREAD_POOL_MAX` | `thread_pool_max` | `8` |
200
200
  | `FLOWJET_THREAD_POOL_IDLE_TIMEOUT` | `thread_pool_idle_timeout` | `300` |
@@ -72,8 +72,8 @@ with client.responses.stream(
72
72
  | `FLOWJET_MODELS` | `default` | Comma-separated logical model ids |
73
73
  | `FLOWJET_HOST` | `::` | Bind host (dual-stack IPv4/IPv6) |
74
74
  | `FLOWJET_PORT` | `8618` | Bind port |
75
- | `FLOWJET_HOME` | `~/.flowjet` | Root for per-session workspaces |
76
- | `FLOWJET_NANO_CONFIG` | unset | Optional `nano.yml` (else `$SOOTHE_HOME/config/nano.yml`) |
75
+ | `FLOWJET_HOME` | `~/.flowjet` | Root for the config file (`$FLOWJET_HOME/config/nano.yml`) and per-session workspaces |
76
+ | `FLOWJET_NANO_CONFIG` | unset | Optional `nano.yml` (else `$FLOWJET_HOME/config/nano.yml`) |
77
77
  | `FLOWJET_THREAD_POOL_MIN` | `2` | Min isolation worker threads |
78
78
  | `FLOWJET_THREAD_POOL_MAX` | `8` | Max isolation worker threads |
79
79
  | `FLOWJET_THREAD_POOL_IDLE_TIMEOUT` | `300` | Scaled-worker idle exit seconds (`0` = never) |
@@ -35,7 +35,8 @@ make examples-e2e
35
35
  ```
36
36
 
37
37
  The server uses soothe-nano with thread-pool isolation. Nano loads
38
- `~/.soothe/config/nano.yml` (or `FLOWJET_NANO_CONFIG`) and its active router profile.
38
+ `$FLOWJET_HOME/config/nano.yml` (default `~/.flowjet/config/nano.yml`, or
39
+ `FLOWJET_NANO_CONFIG`) and its active router profile.
39
40
 
40
41
  ## Environment
41
42
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "flowjet"
7
- version = "2.0.4"
7
+ version = "2.0.6"
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" }
@@ -31,8 +31,8 @@ classifiers = [
31
31
  dependencies = [
32
32
  # Default CLI runtime (FLOWJET_BACKEND=soothe). soothe is a host layer over
33
33
  # soothe-nano, so nano stays pinned explicitly: soothe only caps it at <2.
34
- "soothe>=1.0.13,<2.0.0",
35
- "soothe-nano>=1.2.28,<1.3.0",
34
+ "soothe>=1.0.14,<2.0.0",
35
+ "soothe-nano>=1.3.0,<1.4.0",
36
36
  ]
37
37
 
38
38
  [project.optional-dependencies]
@@ -44,20 +44,14 @@ class _CompactConsoleFormatter(logging.Formatter):
44
44
  return super().format(record)
45
45
 
46
46
 
47
- _FILE_LOGGING_INSTALLED = False
48
-
49
-
50
47
  def configure_cli_logging(*, verbose: bool = False) -> None:
51
- """Quiet the console for one-shot CLI use and enable file logging by default.
48
+ """Quiet the console for one-shot CLI use.
52
49
 
53
50
  - Opt out of ``browser_use`` import-time root logger setup when unset.
54
51
  - Remove existing root stream handlers (stderr/stdout) so init INFO
55
52
  lines do not interleave with the agent answer.
56
53
  - Prevent ``lastResort`` traceback dumps from soothe tool failures.
57
54
  - When ``verbose``, show WARNING+ as single-line messages on stderr.
58
- - Attach a rotating file handler at ``<FLOWJET_HOME>/logs/cli.log`` so
59
- every CLI run is recorded by default (idempotent across re-invocations
60
- within the same process, e.g. tests).
61
55
  """
62
56
  os.environ.setdefault(_BROWSER_USE_SETUP_LOGGING, "false")
63
57
  _remove_root_console_handlers()
@@ -75,25 +69,6 @@ def configure_cli_logging(*, verbose: bool = False) -> None:
75
69
  if root.level < logging.WARNING:
76
70
  root.setLevel(logging.WARNING)
77
71
 
78
- _ensure_cli_file_logging()
79
-
80
-
81
- def _ensure_cli_file_logging() -> None:
82
- """Attach the rotating file handler for CLI runs (idempotent)."""
83
- global _FILE_LOGGING_INSTALLED
84
- if _FILE_LOGGING_INSTALLED:
85
- return
86
- try:
87
- from flowjet.core.home import cli_log_path, ensure_flowjet_home
88
-
89
- ensure_flowjet_home()
90
- from soothe_nano.logging.setup import setup_logging
91
-
92
- setup_logging(log_file=str(cli_log_path()), extra_logger_names=("flowjet",))
93
- _FILE_LOGGING_INSTALLED = True
94
- except Exception: # pragma: no cover - logging must never break the CLI
95
- pass
96
-
97
72
 
98
73
  def silence_after_plugins(*, verbose: bool = False) -> None:
99
74
  """Re-apply quieting after plugin imports (belt-and-suspenders)."""
@@ -321,7 +296,7 @@ class _HelpFormatter(argparse.RawDescriptionHelpFormatter):
321
296
 
322
297
 
323
298
  def _default_config_help() -> str:
324
- from flowjet.cli.agent import default_config_path
299
+ from flowjet.home import default_config_path
325
300
 
326
301
  return f"Alternate nano.yml (default: {default_config_path()})"
327
302
 
@@ -687,12 +662,6 @@ def main_follow(argv: list[str] | None = None) -> int:
687
662
 
688
663
  def main(argv: list[str] | None = None) -> int:
689
664
  try:
690
- # Align SOOTHE_HOME with FLOWJET_HOME before any soothe import so
691
- # config, data, and logs all land under ~/.flowjet. Must run first:
692
- # soothe_sdk.paths binds SOOTHE_HOME at import time.
693
- from flowjet.core.home import ensure_flowjet_home
694
-
695
- ensure_flowjet_home()
696
665
  # Parse first so ``-V`` / ``-h`` / ``--help`` exit before we touch the
697
666
  # logging stack (and the heavy ``flowjet.cli.agent`` import it used to
698
667
  # drag in). Version/help must stay instant.
@@ -419,13 +419,14 @@ def _split_complete_argv(raw: list[str]) -> tuple[list[str], list[str]]:
419
419
  def run_complete(argv: list[str] | None = None) -> int:
420
420
  """Hidden shell protocol: print one candidate per stdout line."""
421
421
  from flowjet.cli.cli import resolve_cli_prog
422
+ from flowjet.home import default_config_path
422
423
 
423
424
  parser = argparse.ArgumentParser(prog=f"{resolve_cli_prog()} __complete", add_help=True)
424
425
  parser.add_argument(
425
426
  "-c",
426
427
  "--config",
427
428
  metavar="PATH",
428
- help="nano.yml path (default: ~/.soothe/config/nano.yml)",
429
+ help=f"nano.yml path (default: {default_config_path()})",
429
430
  )
430
431
  parser.add_argument(
431
432
  "--no-llm",
@@ -46,8 +46,8 @@ REFERENCE_SKILLS: tuple[dict[str, Any], ...] = (
46
46
 
47
47
 
48
48
  def _build_parser() -> argparse.ArgumentParser:
49
- from flowjet.cli.agent import default_config_path
50
49
  from flowjet.cli.cli import resolve_cli_prog
50
+ from flowjet.home import default_config_path
51
51
 
52
52
  parser = argparse.ArgumentParser(
53
53
  prog=f"{resolve_cli_prog()} doctor",
@@ -21,7 +21,7 @@ except ImportError: # pragma: no cover - Windows
21
21
  import yaml
22
22
  from soothe_nano.config import _ENV_VAR_RE, _resolve_env
23
23
 
24
- from flowjet.cli.agent import default_config_path
24
+ from flowjet.home import default_config_path, ensure_flowjet_config
25
25
 
26
26
  DEFAULT_PROVIDER_NAME = "local"
27
27
  DEFAULT_NEW_PROVIDER_NAME = "flowjet-default"
@@ -65,6 +65,10 @@ def run_setup(config_path: str | None = None) -> int:
65
65
 
66
66
  def _run_setup(config_path: str | None = None) -> int:
67
67
  path = Path(config_path).expanduser() if config_path else default_config_path()
68
+ if config_path is None:
69
+ # ``fj setup`` bypasses ``load_config``, so it pulls in a ``~/.soothe/config``
70
+ # copy itself before reading the provider list.
71
+ ensure_flowjet_config()
68
72
  existing = _load_existing_config(path)
69
73
  providers = _list_providers(existing)
70
74
  _, seed_model = _seed_from_config(existing)
@@ -100,27 +104,39 @@ def _run_setup(config_path: str | None = None) -> int:
100
104
  api_key = _prompt_secret_with_default("API key", api_key_default)
101
105
 
102
106
  sys.stdout.write("Fetching available models...\n")
107
+ models: list[str] = []
108
+ fetch_issue: str | None = None
103
109
  try:
104
110
  resolved_endpoint = resolve_config_value(endpoint, field="API endpoint")
105
111
  resolved_api_key = resolve_config_value(api_key, field="API key")
106
- models = fetch_models(resolved_endpoint, resolved_api_key)
107
112
  except RuntimeError as exc:
108
- sys.stderr.write(f"error: {exc}\n")
109
- return 1
110
-
111
- if not models:
112
- sys.stderr.write("error: no models returned by endpoint\n")
113
- return 1
114
-
115
- preferred = seed_model
116
- if selected:
117
- selected_models = selected.get("models")
118
- if isinstance(selected_models, list) and selected_models:
119
- preferred = str(selected_models[0])
120
- if preferred and preferred in models:
121
- models = [preferred] + [m for m in models if m != preferred]
113
+ fetch_issue = str(exc)
114
+ else:
115
+ try:
116
+ models = fetch_models(resolved_endpoint, resolved_api_key)
117
+ except RuntimeError as exc:
118
+ fetch_issue = str(exc)
119
+ if not models and fetch_issue is None:
120
+ fetch_issue = f"{resolved_endpoint} returned no models"
121
+
122
+ if fetch_issue:
123
+ # An unreachable endpoint is not fatal: save the provider anyway so the
124
+ # user can start the model server later and pick a model by hand now.
125
+ sys.stderr.write(f"warning: could not list models ({fetch_issue})\n")
126
+ sys.stderr.write("warning: continuing without a model list\n")
127
+
128
+ if models:
129
+ preferred = seed_model
130
+ if selected:
131
+ selected_models = selected.get("models")
132
+ if isinstance(selected_models, list) and selected_models:
133
+ preferred = str(selected_models[0])
134
+ if preferred and preferred in models:
135
+ models = [preferred] + [m for m in models if m != preferred]
136
+ model = choose_model_interactive(models)
137
+ else:
138
+ model = _prompt_model_with_fallback(_fallback_model_candidates(selected, seed_model))
122
139
 
123
- model = choose_model_interactive(models)
124
140
  if not model:
125
141
  sys.stderr.write("setup cancelled\n")
126
142
  return 1
@@ -139,6 +155,10 @@ def _run_setup(config_path: str | None = None) -> int:
139
155
  sys.stdout.write(f"Saved config to: {path}\n")
140
156
  sys.stdout.write(f"Active profile: {profile}\n")
141
157
  sys.stdout.write(f"Configured model: {provider_name}:{model}\n")
158
+ if fetch_issue:
159
+ sys.stdout.write(
160
+ "Note: model list was not verified; start the endpoint and run 'fj doctor' to confirm.\n"
161
+ )
142
162
  return 0
143
163
 
144
164
 
@@ -464,6 +484,45 @@ def _parse_model_filter(choice: str) -> str:
464
484
  return raw
465
485
 
466
486
 
487
+ def _fallback_model_candidates(selected: dict[str, Any], seed_model: str | None) -> list[str]:
488
+ """Models already known from config, used when the endpoint cannot be queried."""
489
+ candidates: list[str] = []
490
+ if selected:
491
+ models = selected.get("models")
492
+ if isinstance(models, list):
493
+ candidates.extend(str(item) for item in models if str(item).strip())
494
+ if seed_model and seed_model not in candidates:
495
+ candidates.insert(0, seed_model)
496
+ return candidates
497
+
498
+
499
+ def _prompt_model_with_fallback(candidates: list[str]) -> str | None:
500
+ """Ask for a model id directly when the endpoint has no model list to pick from."""
501
+ default = candidates[0] if candidates else DEFAULT_MODEL
502
+ if candidates:
503
+ sys.stdout.write("Models saved for this provider:\n")
504
+ for idx, name in enumerate(candidates, start=1):
505
+ sys.stdout.write(f" {idx}. {name}\n")
506
+ label = f"Model name or number 1-{len(candidates)}"
507
+ else:
508
+ label = "Model name"
509
+
510
+ while True:
511
+ try:
512
+ value = input(f"{label} [{default}]: ").strip()
513
+ except EOFError:
514
+ return default
515
+ if not value:
516
+ return default
517
+ if value.isdigit() and candidates:
518
+ idx = int(value)
519
+ if 1 <= idx <= len(candidates):
520
+ return candidates[idx - 1]
521
+ sys.stdout.write(f"Invalid number; enter 1-{len(candidates)} or a model name.\n")
522
+ continue
523
+ return value
524
+
525
+
467
526
  def _load_existing_config(path: Path) -> dict[str, Any]:
468
527
  if not path.is_file():
469
528
  return {}
@@ -18,7 +18,7 @@ from contextlib import asynccontextmanager
18
18
  from pathlib import Path
19
19
  from typing import Any
20
20
 
21
- from soothe_nano.config import SOOTHE_HOME, SootheConfig
21
+ from soothe_nano.config import SootheConfig
22
22
  from soothe_nano.resolve import resolve_checkpointer
23
23
 
24
24
  from flowjet.core.backend import Backend, resolve_backend
@@ -29,6 +29,8 @@ from flowjet.core.modes import (
29
29
  SurfaceProfile,
30
30
  require_mode,
31
31
  )
32
+ from flowjet.home import default_config_path as _flowjet_config_path
33
+ from flowjet.home import ensure_flowjet_config
32
34
 
33
35
  logger = logging.getLogger(__name__)
34
36
 
@@ -75,8 +77,8 @@ def register_flowjet_builtin_skills() -> None:
75
77
 
76
78
 
77
79
  def default_config_path() -> Path:
78
- """Return ``~/.soothe/config/nano.yml`` (respects ``SOOTHE_HOME``)."""
79
- return SOOTHE_HOME / "config" / "nano.yml"
80
+ """Return ``$FLOWJET_HOME/config/nano.yml`` (default ``~/.flowjet/config/nano.yml``)."""
81
+ return _flowjet_config_path()
80
82
 
81
83
 
82
84
  def load_config(
@@ -93,14 +95,21 @@ def load_config(
93
95
 
94
96
  Resolution order:
95
97
  1. Explicit ``config_path``
96
- 2. ``SOOTHE_HOME / config / nano.yml`` (default ``~/.soothe/config/nano.yml``)
98
+ 2. ``$FLOWJET_HOME / config / nano.yml`` (default ``~/.flowjet/config/nano.yml``)
97
99
  3. Zero-config ``SootheConfig()`` from ``OPENAI_API_KEY`` / ``ANTHROPIC_API_KEY``
98
100
 
99
101
  On the ``soothe`` backend, a sibling ``soothe.yml`` (host-owned keys) is
100
102
  composed over ``nano.yml`` when present.
103
+
104
+ Without an explicit ``config_path``, a config found in ``~/.soothe/config`` is
105
+ copied into the flowjet home first — see :func:`flowjet.home.ensure_flowjet_config`.
101
106
  """
102
107
  backend = backend or resolve_backend(Surface.CLI)
103
- path = Path(config_path).expanduser() if config_path else default_config_path()
108
+ if config_path is None:
109
+ ensure_flowjet_config()
110
+ path = default_config_path()
111
+ else:
112
+ path = Path(config_path).expanduser()
104
113
  if path.is_file():
105
114
  return _load_config_file(path, backend)
106
115
  if config_path is not None:
@@ -33,6 +33,13 @@ _SKIP_CUSTOM_TYPES = frozenset(
33
33
  }
34
34
  )
35
35
 
36
+ # Host (soothe) executor emits this as a ``mode=custom`` chunk carrying
37
+ # pre-parsed tool kwargs — a structured wire event that bypasses the fragile
38
+ # ``tool_call_chunks`` partial-JSON accumulation. Handled in
39
+ # ``iter_nano_runtime_events`` (not ``map_custom``) so it can yield a real
40
+ # ``ToolStarted`` and feed the accumulator's arg overlays.
41
+ _STREAM_TOOL_CALL_UPDATE = "soothe.stream.tool_call.update"
42
+
36
43
  # Host (soothe) middleware surfaces as ``<Middleware>.<phase>`` graph updates.
37
44
  # The CLI renders these through ``friendly_progress``, so the synthetic payloads
38
45
  # below stay in the same ``soothe.*`` vocabulary as nano's custom events.
@@ -216,6 +223,10 @@ def map_custom(data: dict[str, Any]) -> Progress | None:
216
223
  event_type = str(data.get("type") or "").strip()
217
224
  if not event_type or event_type in _SKIP_CUSTOM_TYPES:
218
225
  return None
226
+ if event_type == _STREAM_TOOL_CALL_UPDATE:
227
+ # Handled in ``iter_nano_runtime_events`` — yields a ToolStarted /
228
+ # Progress refresh with the pre-parsed args, never a generic Progress.
229
+ return None
219
230
  if event_type.startswith("soothe.output."):
220
231
  return None
221
232
  short = event_type[7:] if event_type.startswith("soothe.") else event_type
@@ -232,6 +243,61 @@ def map_custom(data: dict[str, Any]) -> Progress | None:
232
243
  return Progress(stage=str(stage), message=str(message or "Working"), raw=data)
233
244
 
234
245
 
246
+ def _wire_tool_call_update(
247
+ data: dict[str, Any],
248
+ *,
249
+ emitted_calls: set[str],
250
+ refreshed_calls: set[str],
251
+ tool_args: ToolCallArgAccumulator,
252
+ ) -> list[RuntimeEvent]:
253
+ """Convert a ``soothe.stream.tool_call.update`` into ToolStarted / Progress.
254
+
255
+ The host (soothe) executor pre-parses tool kwargs and emits them as a
256
+ structured wire event — an efficient transit path that does not depend on
257
+ ``tool_call_chunks`` partial-JSON accumulation. This makes that path
258
+ first-class: a new call id yields ``ToolStarted`` (same as the
259
+ ``messages`` path), and a repeat yields a ``Progress`` refresh so the
260
+ status line picks up the parsed args.
261
+
262
+ Args are also fed into ``tool_args`` overlays so the eventual
263
+ ``ToolCompleted`` event carries them even if the ``messages``-mode chunks
264
+ never produced parseable JSON.
265
+ """
266
+ tc_id = str(data.get("tool_call_id") or "").strip()
267
+ name = str(data.get("name") or "").strip()
268
+ args = data.get("args")
269
+ if not isinstance(args, dict):
270
+ args = {}
271
+ if not tc_id:
272
+ return []
273
+ # Seed the accumulator's overlay so ToolCompleted (messages path) sees
274
+ # the parsed args even if tool_call_chunks were empty or partial.
275
+ tool_args.seed_overlay(tc_id, name=name, args=args)
276
+ # Quiet tools (write_todos) are plan bookkeeping that already surfaces as
277
+ # step progress — never emit a ToolStarted for them.
278
+ if name in _QUIET_TOOLS:
279
+ return []
280
+ if tc_id in emitted_calls:
281
+ # Already started via the messages path; refresh the status line with
282
+ # the parsed args (Writing a.txt, not bare Writing).
283
+ if args and tc_id not in refreshed_calls:
284
+ refreshed_calls.add(tc_id)
285
+ return [
286
+ Progress(
287
+ stage="Tool",
288
+ message=tool_call_summary(name or "tool", args),
289
+ raw={
290
+ "type": "soothe.tool.started",
291
+ "tool": name,
292
+ "args": args,
293
+ },
294
+ )
295
+ ]
296
+ return []
297
+ emitted_calls.add(tc_id)
298
+ return [ToolStarted(tool=name or "tool", call_id=tc_id, args=args or None)]
299
+
300
+
235
301
  async def iter_nano_runtime_events(
236
302
  agent: Any,
237
303
  *,
@@ -284,6 +350,18 @@ async def iter_nano_runtime_events(
284
350
  _ns, mode, data = chunk
285
351
 
286
352
  if mode == "custom" and isinstance(data, dict):
353
+ # Host (soothe) wire tool-call update: pre-parsed args that
354
+ # bypass tool_call_chunks partial-JSON accumulation. Handle
355
+ # before map_custom so it yields a real ToolStarted.
356
+ if str(data.get("type") or "").strip() == _STREAM_TOOL_CALL_UPDATE:
357
+ for event in _wire_tool_call_update(
358
+ data,
359
+ emitted_calls=emitted_calls,
360
+ refreshed_calls=refreshed_calls,
361
+ tool_args=tool_args,
362
+ ):
363
+ yield event
364
+ continue
287
365
  mapped = map_custom(data)
288
366
  if mapped:
289
367
  yield mapped
@@ -244,3 +244,32 @@ class ToolCallArgAccumulator:
244
244
  name = str(state.get("name") or "") or None
245
245
  args = _merge_args(parse_partial_args(str(state.get("args_str") or "")), overlay)
246
246
  return name, args
247
+
248
+ def seed_overlay(
249
+ self,
250
+ tool_call_id: str,
251
+ *,
252
+ name: str = "",
253
+ args: dict[str, Any] | None = None,
254
+ ) -> None:
255
+ """Seed name + parsed args for a call from a wire (non-chunk) source.
256
+
257
+ The host (soothe) executor emits ``soothe.stream.tool_call.update``
258
+ custom events with pre-parsed kwargs. Feeding them here lets
259
+ ``ToolCompleted`` (messages path) resolve the name and args even when
260
+ ``tool_call_chunks`` streamed empty/partial JSON — the overlay is the
261
+ fallback in ``_resolved_args`` / ``pop``.
262
+ """
263
+ tc_id = str(tool_call_id or "").strip()
264
+ if not tc_id:
265
+ return
266
+ if name:
267
+ state = self._ensure(tc_id)
268
+ if not state.get("name"):
269
+ state["name"] = name
270
+ self._last_active_id = tc_id
271
+ if isinstance(args, dict) and args:
272
+ prev = self._overlays.get(tc_id) or {}
273
+ merged = _merge_args(prev, args)
274
+ if merged != prev:
275
+ self._overlays[tc_id] = merged