flowjet 2.0.2__tar.gz → 2.0.3__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 (168) hide show
  1. {flowjet-2.0.2 → flowjet-2.0.3}/CHANGELOG.md +11 -0
  2. {flowjet-2.0.2 → flowjet-2.0.3}/PKG-INFO +1 -1
  3. {flowjet-2.0.2 → flowjet-2.0.3}/pyproject.toml +1 -1
  4. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/cli/progress.py +34 -0
  5. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/bridges/nano/mapping.py +193 -2
  6. {flowjet-2.0.2 → flowjet-2.0.3}/.gitignore +0 -0
  7. {flowjet-2.0.2 → flowjet-2.0.3}/LICENSE +0 -0
  8. {flowjet-2.0.2 → flowjet-2.0.3}/README.md +0 -0
  9. {flowjet-2.0.2 → flowjet-2.0.3}/config/nano.yml +0 -0
  10. {flowjet-2.0.2 → flowjet-2.0.3}/deploy/README.md +0 -0
  11. {flowjet-2.0.2 → flowjet-2.0.3}/deploy/nano.yml +0 -0
  12. {flowjet-2.0.2 → flowjet-2.0.3}/docs/completion-design.md +0 -0
  13. {flowjet-2.0.2 → flowjet-2.0.3}/docs/drafts/README.md +0 -0
  14. {flowjet-2.0.2 → flowjet-2.0.3}/docs/drafts/api-spec.md +0 -0
  15. {flowjet-2.0.2 → flowjet-2.0.3}/docs/impl/IG-001-openai-compatible-server.md +0 -0
  16. {flowjet-2.0.2 → flowjet-2.0.3}/docs/impl/IG-002-isolated-thread-pool-runtime.md +0 -0
  17. {flowjet-2.0.2 → flowjet-2.0.3}/docs/impl/IG-003-production-isolation-hardening.md +0 -0
  18. {flowjet-2.0.2 → flowjet-2.0.3}/docs/impl/README.md +0 -0
  19. {flowjet-2.0.2 → flowjet-2.0.3}/docs/impl/TEST-COVERAGE-MATRIX.md +0 -0
  20. {flowjet-2.0.2 → flowjet-2.0.3}/docs/naming.md +0 -0
  21. {flowjet-2.0.2 → flowjet-2.0.3}/docs/server.md +0 -0
  22. {flowjet-2.0.2 → flowjet-2.0.3}/docs/specs/RFC-001-openai-compatible-api.md +0 -0
  23. {flowjet-2.0.2 → flowjet-2.0.3}/docs/specs/RFC-002-isolated-thread-pool-runtime.md +0 -0
  24. {flowjet-2.0.2 → flowjet-2.0.3}/docs/specs/RFC-003-production-isolation-hardening.md +0 -0
  25. {flowjet-2.0.2 → flowjet-2.0.3}/docs/specs/RFC-004-unified-package.md +0 -0
  26. {flowjet-2.0.2 → flowjet-2.0.3}/docs/specs/rfc-history.md +0 -0
  27. {flowjet-2.0.2 → flowjet-2.0.3}/docs/specs/rfc-index.md +0 -0
  28. {flowjet-2.0.2 → flowjet-2.0.3}/docs/specs/rfc-namings.md +0 -0
  29. {flowjet-2.0.2 → flowjet-2.0.3}/docs/specs/templates/architecture-design.md +0 -0
  30. {flowjet-2.0.2 → flowjet-2.0.3}/docs/specs/templates/conceptual-design.md +0 -0
  31. {flowjet-2.0.2 → flowjet-2.0.3}/docs/specs/templates/impl-interface-design.md +0 -0
  32. {flowjet-2.0.2 → flowjet-2.0.3}/docs/specs/templates/rfc-standard.md +0 -0
  33. {flowjet-2.0.2 → flowjet-2.0.3}/docs/specs/templates/rfc-template.md +0 -0
  34. {flowjet-2.0.2 → flowjet-2.0.3}/docs/upgrade.md +0 -0
  35. {flowjet-2.0.2 → flowjet-2.0.3}/examples/README.md +0 -0
  36. {flowjet-2.0.2 → flowjet-2.0.3}/nano.yml +0 -0
  37. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/__init__.py +0 -0
  38. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/__main__.py +0 -0
  39. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/README.md +0 -0
  40. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/gws-drive/SKILL.md +0 -0
  41. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/gws-drive-upload/SKILL.md +0 -0
  42. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/logo-generator-skill/.env.example +0 -0
  43. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/logo-generator-skill/README.md +0 -0
  44. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/logo-generator-skill/SKILL.md +0 -0
  45. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/logo-generator-skill/assets/background_library.html +0 -0
  46. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/logo-generator-skill/assets/showcase_template.html +0 -0
  47. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/logo-generator-skill/references/background_styles.md +0 -0
  48. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/logo-generator-skill/references/design_patterns.md +0 -0
  49. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/logo-generator-skill/references/webgl_backgrounds.md +0 -0
  50. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/logo-generator-skill/requirements.txt +0 -0
  51. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/logo-generator-skill/scripts/generate_showcase.py +0 -0
  52. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/logo-generator-skill/scripts/svg_to_png.py +0 -0
  53. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/SKILL.md +0 -0
  54. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/assets/brainstorming/spec-document-reviewer-prompt.md +0 -0
  55. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/assets/implementation/coding-plan-template.md +0 -0
  56. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/assets/implementation/impl-guide-template.md +0 -0
  57. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/assets/review/pr-review-template.md +0 -0
  58. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/assets/review/review-checklist.md +0 -0
  59. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/assets/specs/rfc-template.md +0 -0
  60. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-architecture-design.md +0 -0
  61. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-conceptual-design.md +0 -0
  62. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-impl-interface-design.md +0 -0
  63. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-drafts-readme.md +0 -0
  64. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-impl-readme.md +0 -0
  65. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-platonic.yml +0 -0
  66. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-history.md +0 -0
  67. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-index.md +0 -0
  68. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-namings.md +0 -0
  69. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-standard.md +0 -0
  70. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/BRAINSTORM/brainstorm.md +0 -0
  71. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-code.md +0 -0
  72. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-create-guide.md +0 -0
  73. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-full.md +0 -0
  74. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-update-guide.md +0 -0
  75. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-validate-guide.md +0 -0
  76. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-plan-modular-specs.md +0 -0
  77. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-architecture.md +0 -0
  78. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-conceptual.md +0 -0
  79. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-impl-interface.md +0 -0
  80. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-scaffold.md +0 -0
  81. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-scan.md +0 -0
  82. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/REFERENCE.md +0 -0
  83. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/REVIEW/review-spec-compliance.md +0 -0
  84. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-check-compliance.md +0 -0
  85. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-check-taxonomy.md +0 -0
  86. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-history.md +0 -0
  87. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-index.md +0 -0
  88. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-namings.md +0 -0
  89. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-refine.md +0 -0
  90. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-validate-consistency.md +0 -0
  91. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-overview.md +0 -0
  92. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-1.md +0 -0
  93. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-2.md +0 -0
  94. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-3.md +0 -0
  95. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/research-bootstrap/SKILL.md +0 -0
  96. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/research-bootstrap/patterns/evidence-deep.json +0 -0
  97. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/research-bootstrap/patterns/evidence-first.json +0 -0
  98. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/research-bootstrap/patterns/idea-first.json +0 -0
  99. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/research-bootstrap/patterns/loop.json +0 -0
  100. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/research-bootstrap/patterns/paper-pipeline.json +0 -0
  101. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/research-bootstrap/patterns/rapid.json +0 -0
  102. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/research-bootstrap/patterns/rigor-first.json +0 -0
  103. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/research-bootstrap/patterns/stance-first.json +0 -0
  104. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/research-bootstrap/patterns/systematic-review.json +0 -0
  105. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/research-bootstrap/patterns/verify-loop.json +0 -0
  106. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/research-bootstrap/references/backends.md +0 -0
  107. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/research-bootstrap/references/handoff-ars-omr.md +0 -0
  108. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/research-bootstrap/references/handoff-omr-ars.md +0 -0
  109. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/research-bootstrap/references/routing.md +0 -0
  110. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/research-bootstrap/scripts/csljson_to_omr.py +0 -0
  111. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/builtin_skills/research-bootstrap/scripts/papers_index_to_csljson.py +0 -0
  112. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/cli/__init__.py +0 -0
  113. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/cli/__main__.py +0 -0
  114. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/cli/agent.py +0 -0
  115. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/cli/cli.py +0 -0
  116. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/cli/completion/__init__.py +0 -0
  117. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/cli/completion/context.py +0 -0
  118. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/cli/completion/engine.py +0 -0
  119. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/cli/doctor_cmd.py +0 -0
  120. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/cli/doctor_fix_cmd.py +0 -0
  121. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/cli/serve_cmd.py +0 -0
  122. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/cli/setup_cmd.py +0 -0
  123. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/cli/shell/__init__.py +0 -0
  124. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/cli/shell/_fj.zsh +0 -0
  125. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/cli/shell/fj.bash +0 -0
  126. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/cli/stream.py +0 -0
  127. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/cli/threads.py +0 -0
  128. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/cli/tool_stream.py +0 -0
  129. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/__init__.py +0 -0
  130. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/backend.py +0 -0
  131. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/backends/__init__.py +0 -0
  132. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/backends/direct.py +0 -0
  133. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/backends/fake.py +0 -0
  134. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/backends/isolation/__init__.py +0 -0
  135. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/backends/isolation/adapter.py +0 -0
  136. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/backends/isolation/admission.py +0 -0
  137. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/backends/isolation/backend.py +0 -0
  138. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/backends/isolation/errors.py +0 -0
  139. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/backends/isolation/mode_gate.py +0 -0
  140. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/backends/isolation/pool.py +0 -0
  141. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/backends/isolation/request.py +0 -0
  142. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/backends/isolation/workspace.py +0 -0
  143. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/bootstrap.py +0 -0
  144. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/bridges/nano/__init__.py +0 -0
  145. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/bridges/nano/adapter.py +0 -0
  146. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/bridges/nano/backend.py +0 -0
  147. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/events.py +0 -0
  148. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/identity.py +0 -0
  149. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/modes.py +0 -0
  150. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/protocol.py +0 -0
  151. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/tool_results.py +0 -0
  152. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/core/tool_stream.py +0 -0
  153. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/server/__init__.py +0 -0
  154. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/server/__main__.py +0 -0
  155. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/server/acp/__init__.py +0 -0
  156. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/server/acp/agent.py +0 -0
  157. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/server/acp/routes.py +0 -0
  158. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/server/config.py +0 -0
  159. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/server/http/__init__.py +0 -0
  160. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/server/http/app.py +0 -0
  161. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/server/http/auth.py +0 -0
  162. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/server/openai_compat/__init__.py +0 -0
  163. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/server/openai_compat/errors.py +0 -0
  164. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/server/openai_compat/projection.py +0 -0
  165. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/server/openai_compat/routes.py +0 -0
  166. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/server/openai_compat/schemas.py +0 -0
  167. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/server/openai_compat/service.py +0 -0
  168. {flowjet-2.0.2 → flowjet-2.0.3}/src/flowjet/server/openai_compat/store.py +0 -0
@@ -10,6 +10,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [2.0.3] — 2026-09-18
14
+
15
+ ### Added
16
+
17
+ - **Plan-step and middleware-phase progress in the CLI.** The nano bridge now
18
+ maps the host (soothe) loop's todo list and middleware phase updates into
19
+ step/tool progress events, so the terminal shows `Step 2/3 · Create a2.txt`
20
+ instead of a bare `Working` line. Late-parsing tool args refresh the status
21
+ line once, and `write_todos` bookkeeping is suppressed as a tool call (it
22
+ already drives the step line).
23
+
13
24
  ## [2.0.2] — 2026-09-18
14
25
 
15
26
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: flowjet
3
- Version: 2.0.2
3
+ Version: 2.0.3
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.2"
7
+ version = "2.0.3"
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" }
@@ -67,6 +67,14 @@ _ACTION_LABELS: dict[str, str] = {
67
67
  "queued": "Queued",
68
68
  }
69
69
 
70
+ # Host (soothe) loop phases surfaced as graph updates by the nano bridge.
71
+ _STEP_PHASE_LABELS: dict[str, str] = {
72
+ "planned": "Planning steps",
73
+ "decomposed": "Decomposing task",
74
+ "evaluated": "Evaluating step",
75
+ "fanned_out": "Coordinating agents",
76
+ }
77
+
70
78
  # Verb + color for well-known tools (fallback: Running / yellow)
71
79
  _TOOL_VERBS: dict[str, tuple[str, str]] = {
72
80
  "read_file": ("Reading", "yellow"),
@@ -693,6 +701,32 @@ def friendly_progress(data: dict[str, Any]) -> tuple[str, str] | None:
693
701
  label = f"{label} · {detail}"
694
702
  return _fit(label), color
695
703
 
704
+ if domain == "step":
705
+ # Plan steps come from the agent's todo list (one event per change).
706
+ if "steps" in data or "todo" in data:
707
+ step = data.get("step")
708
+ steps = data.get("steps")
709
+ todo = _compact(data.get("todo"))
710
+ head = f"Step {step}/{steps}" if step and steps else "Step"
711
+ if todo:
712
+ return _fit(f"{head} · {todo}"), "blue"
713
+ if data.get("status") == "completed":
714
+ return _fit(f"Steps complete · {step}/{steps}"), "green"
715
+ return _fit(head), "blue"
716
+ label = _STEP_PHASE_LABELS.get(action) or verb
717
+ return _fit(label), "blue"
718
+
719
+ if event_type == "soothe.ask.requested":
720
+ question = _compact(data.get("question") or data.get("message"))
721
+ if question:
722
+ q_limit = _detail_budget(prefix="Preparing question · ", fraction=0.55, floor=16)
723
+ return _fit(f"Preparing question · {_truncate_middle(question, q_limit)}"), "magenta"
724
+ return _fit("Preparing question"), "magenta"
725
+
726
+ if event_type == "soothe.intake.routed":
727
+ name = _compact(data.get("subagent") or data.get("subagent_type") or "specialist")
728
+ return _fit(f"Routing · {name}"), "magenta"
729
+
696
730
  if domain == "skill" or "skill" in short:
697
731
  color = "blue"
698
732
  name = data.get("skill") or data.get("name") or "skill"
@@ -33,6 +33,161 @@ _SKIP_CUSTOM_TYPES = frozenset(
33
33
  }
34
34
  )
35
35
 
36
+ # Host (soothe) middleware surfaces as ``<Middleware>.<phase>`` graph updates.
37
+ # The CLI renders these through ``friendly_progress``, so the synthetic payloads
38
+ # below stay in the same ``soothe.*`` vocabulary as nano's custom events.
39
+ _HOST_PHASE_EVENTS: dict[str, str] = {
40
+ "GoalStepGuardMiddleware": "soothe.step.planned",
41
+ "DecomposeTaskMiddleware": "soothe.step.decomposed",
42
+ "EvalStepMiddleware": "soothe.step.evaluated",
43
+ "WestWorldMiddleware": "soothe.step.fanned_out",
44
+ "AskUserPromptMiddleware": "soothe.ask.requested",
45
+ "IntakeOnlyTaskGuardMiddleware": "soothe.intake.routed",
46
+ }
47
+
48
+ # Plan bookkeeping: the todo list already drives the step line.
49
+ _QUIET_TOOLS = frozenset({"write_todos", "todo_write", "write_todo_list"})
50
+
51
+ # Fires on every turn with no user-visible content.
52
+ _SKIP_UPDATE_NODES = frozenset(
53
+ {
54
+ "PatchToolCallsMiddleware",
55
+ "ProgressiveToolMiddleware",
56
+ "WorkspaceContextMiddleware",
57
+ "TodoListMiddleware",
58
+ "model",
59
+ "tools",
60
+ }
61
+ )
62
+
63
+
64
+ def todo_step_payload(todos: list[Any]) -> dict[str, Any] | None:
65
+ """Turn a soothe todo list into a step-progress payload.
66
+
67
+ The agent writes its plan as todos (``write_todos``); each ``in_progress``
68
+ item is the step the user is waiting on, which is far more informative than
69
+ a generic "Working" line.
70
+ """
71
+ items = [item for item in todos if isinstance(item, dict)]
72
+ if not items:
73
+ return None
74
+ total = len(items)
75
+ done = sum(1 for item in items if str(item.get("status")) == "completed")
76
+ current = next(
77
+ (item for item in items if str(item.get("status")) == "in_progress"),
78
+ None,
79
+ )
80
+ if current is None:
81
+ if done == 0:
82
+ return None
83
+ return {
84
+ "type": "soothe.step.progress",
85
+ "step": done,
86
+ "steps": total,
87
+ "done": done,
88
+ "status": "completed",
89
+ "todo": "",
90
+ }
91
+ return {
92
+ "type": "soothe.step.progress",
93
+ "step": items.index(current) + 1,
94
+ "steps": total,
95
+ "done": done,
96
+ "status": "in_progress",
97
+ "todo": str(current.get("content") or ""),
98
+ }
99
+
100
+
101
+ def _skill_names(payload: dict[str, Any]) -> list[str]:
102
+ """Best-effort skill names from a ``SkillActivationMiddleware`` payload."""
103
+ raw = payload.get("skill_activation")
104
+ if isinstance(raw, dict):
105
+ raw = raw.get("skills") or raw.get("activated") or raw.get("names") or []
106
+ if not isinstance(raw, (list, tuple)):
107
+ return []
108
+ names: list[str] = []
109
+ for item in raw:
110
+ if isinstance(item, str) and item.strip():
111
+ names.append(item.strip())
112
+ elif isinstance(item, dict):
113
+ name = item.get("name") or item.get("skill")
114
+ if isinstance(name, str) and name.strip():
115
+ names.append(name.strip())
116
+ return names
117
+
118
+
119
+ def update_progress(data: dict[str, Any]) -> tuple[str, dict[str, Any]] | None:
120
+ """Map one soothe ``updates`` chunk to ``(dedupe_key, event_payload)``.
121
+
122
+ Returns ``None`` for the plumbing updates that carry no user-visible step.
123
+ """
124
+ todos = data.get("todos")
125
+ if isinstance(todos, list):
126
+ payload = todo_step_payload(todos)
127
+ if payload is not None:
128
+ key = f"todo:{payload['step']}/{payload['steps']}:{payload['todo']}:{payload['done']}"
129
+ return key, payload
130
+
131
+ for node, node_payload in data.items():
132
+ name = node.split(".")[0]
133
+ # The todo list rides along inside the ``tools`` node update.
134
+ if isinstance(node_payload, dict) and isinstance(node_payload.get("todos"), list):
135
+ payload = todo_step_payload(node_payload["todos"])
136
+ if payload is not None:
137
+ key = (
138
+ f"todo:{payload['step']}/{payload['steps']}:{payload['todo']}:{payload['done']}"
139
+ )
140
+ return key, payload
141
+ if name in _SKIP_UPDATE_NODES:
142
+ continue
143
+ event = _HOST_PHASE_EVENTS.get(name)
144
+ if event is not None:
145
+ phase = node.split(".", 1)[1] if "." in node else ""
146
+ return node, {"type": event, "middleware": name, "phase": phase}
147
+ if name == "SkillActivationMiddleware" and isinstance(node_payload, dict):
148
+ names = _skill_names(node_payload)
149
+ if names:
150
+ return node, {"type": "soothe.skill.activated", "skill": ", ".join(names)}
151
+ return None
152
+
153
+
154
+ # Arg keys worth echoing in a one-line tool summary (first match wins).
155
+ _TOOL_SUMMARY_KEYS = (
156
+ "file_path",
157
+ "path",
158
+ "target_file",
159
+ "command",
160
+ "cmd",
161
+ "script",
162
+ "query",
163
+ "pattern",
164
+ "url",
165
+ "skill",
166
+ )
167
+
168
+
169
+ def tool_call_summary(name: str, args: dict[str, Any]) -> str:
170
+ """``write_file a.txt``-style summary for renderers without tool metadata."""
171
+ for key in _TOOL_SUMMARY_KEYS:
172
+ value = args.get(key)
173
+ if isinstance(value, (str, int, float)) and str(value).strip():
174
+ text = " ".join(str(value).split())
175
+ return f"{name} {text[:60]}"
176
+ return str(name)
177
+
178
+
179
+ def step_message(payload: dict[str, Any]) -> str:
180
+ """Human-readable one-liner for a step payload (used by non-CLI renderers)."""
181
+ step = payload.get("step")
182
+ steps = payload.get("steps")
183
+ todo = str(payload.get("todo") or "").strip()
184
+ head = f"{step}/{steps}" if step and steps else "step"
185
+ if todo:
186
+ return f"Step {head} · {todo}"
187
+ if payload.get("status") == "completed":
188
+ return f"Steps complete · {head}"
189
+ return f"Step {head}"
190
+
36
191
 
37
192
  def resolve_interaction_mode(metadata: dict[str, Any] | None) -> str:
38
193
  """Return ``agent`` or ``ask`` from request metadata (default ``agent``)."""
@@ -114,6 +269,8 @@ async def iter_nano_runtime_events(
114
269
  open_args: dict[str, dict[str, Any]] = {}
115
270
  tool_args = ToolCallArgAccumulator()
116
271
  emitted_calls: set[str] = set()
272
+ refreshed_calls: set[str] = set()
273
+ last_step_key = ""
117
274
 
118
275
  try:
119
276
  async for chunk in agent.astream(
@@ -132,8 +289,23 @@ async def iter_nano_runtime_events(
132
289
  yield mapped
133
290
  continue
134
291
 
135
- if mode == "updates" and isinstance(data, dict) and "__interrupt__" in data:
136
- yield InterruptWaiting(message="Waiting for input…")
292
+ if mode == "updates" and isinstance(data, dict):
293
+ if "__interrupt__" in data:
294
+ yield InterruptWaiting(message="Waiting for input…")
295
+ continue
296
+ # Plan steps (todos) and host middleware phases are the only
297
+ # signal soothe gives us about *what* it is doing between tool
298
+ # calls; surface each change once.
299
+ step = update_progress(data)
300
+ if step is not None:
301
+ key, payload = step
302
+ if key != last_step_key:
303
+ last_step_key = key
304
+ yield Progress(
305
+ stage="Step",
306
+ message=step_message(payload),
307
+ raw=payload,
308
+ )
137
309
  continue
138
310
 
139
311
  if mode != "messages":
@@ -153,6 +325,22 @@ async def iter_nano_runtime_events(
153
325
  if tc_id in emitted_calls:
154
326
  # Later updates refresh args; the call already started.
155
327
  open_args[tc_id] = args
328
+ # Tool args stream as partial JSON, so ToolStarted
329
+ # usually carries none — refresh the status line
330
+ # once they parse (Writing b1.txt, not Writing).
331
+ # Once per call: partial JSON grows on every token,
332
+ # and re-emitting it would flood the event stream.
333
+ if args and name not in _QUIET_TOOLS and tc_id not in refreshed_calls:
334
+ refreshed_calls.add(tc_id)
335
+ yield Progress(
336
+ stage="Tool",
337
+ message=tool_call_summary(str(name), args),
338
+ raw={
339
+ "type": "soothe.tool.started",
340
+ "tool": name,
341
+ "args": args,
342
+ },
343
+ )
156
344
  continue
157
345
  emitted_calls.add(tc_id)
158
346
  open_tools[tc_id] = str(name)
@@ -197,6 +385,9 @@ async def iter_nano_runtime_events(
197
385
  err_detail = tool_result_error_detail(message_obj.content)
198
386
  ok = status != "error" and err_detail is None
199
387
  detail = simplify_tool_error(err_detail) if err_detail else None
388
+ if str(name) in _QUIET_TOOLS and ok:
389
+ # Plan bookkeeping already surfaced as a step event.
390
+ continue
200
391
  yield ToolCompleted(
201
392
  tool=str(name),
202
393
  ok=ok,
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