flowjet 2.0.8__tar.gz → 2.0.10__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 (179) hide show
  1. {flowjet-2.0.8 → flowjet-2.0.10}/.gitignore +3 -1
  2. {flowjet-2.0.8 → flowjet-2.0.10}/CHANGELOG.md +31 -0
  3. {flowjet-2.0.8 → flowjet-2.0.10}/PKG-INFO +5 -3
  4. {flowjet-2.0.8 → flowjet-2.0.10}/deploy/README.md +1 -1
  5. {flowjet-2.0.8 → flowjet-2.0.10}/deploy/nano.yml +1 -1
  6. {flowjet-2.0.8 → flowjet-2.0.10}/pyproject.toml +13 -20
  7. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/cli.py +5 -0
  8. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/progress.py +2 -2
  9. flowjet-2.0.10/src/flowjet/core/_warnings.py +53 -0
  10. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backend.py +10 -7
  11. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backends/isolation/__init__.py +14 -2
  12. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backends/isolation/adapter.py +10 -0
  13. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backends/isolation/backend.py +34 -7
  14. flowjet-2.0.10/src/flowjet/core/backends/isolation/boxlite_protocol.py +161 -0
  15. flowjet-2.0.10/src/flowjet/core/backends/isolation/boxlite_vm_pool.py +668 -0
  16. flowjet-2.0.10/src/flowjet/core/backends/isolation/boxlite_worker.py +256 -0
  17. flowjet-2.0.10/src/flowjet/core/backends/isolation/isolation_pool.py +48 -0
  18. flowjet-2.0.10/src/flowjet/core/backends/isolation/workspace.py +411 -0
  19. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/bootstrap.py +1 -1
  20. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/bridges/nano/backend.py +10 -1
  21. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/bridges/nano/mapping.py +36 -1
  22. flowjet-2.0.10/src/flowjet/core/bridges/soothe/__init__.py +6 -0
  23. flowjet-2.0.10/src/flowjet/core/bridges/soothe/adapter.py +141 -0
  24. flowjet-2.0.10/src/flowjet/core/bridges/soothe/backend.py +47 -0
  25. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/tool_stream.py +1 -1
  26. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/__main__.py +6 -0
  27. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/acp/agent.py +213 -2
  28. flowjet-2.0.10/src/flowjet/server/config.py +174 -0
  29. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/http/app.py +97 -1
  30. flowjet-2.0.8/docs/specs/templates/architecture-design.md +0 -139
  31. flowjet-2.0.8/docs/specs/templates/conceptual-design.md +0 -102
  32. flowjet-2.0.8/docs/specs/templates/impl-interface-design.md +0 -137
  33. flowjet-2.0.8/docs/specs/templates/rfc-standard.md +0 -577
  34. flowjet-2.0.8/src/flowjet/builtin_skills/platonic-coding/assets/specs/rfc-template.md +0 -94
  35. flowjet-2.0.8/src/flowjet/core/backends/isolation/workspace.py +0 -47
  36. flowjet-2.0.8/src/flowjet/server/config.py +0 -70
  37. {flowjet-2.0.8 → flowjet-2.0.10}/LICENSE +0 -0
  38. {flowjet-2.0.8 → flowjet-2.0.10}/README.md +0 -0
  39. {flowjet-2.0.8 → flowjet-2.0.10}/config/nano.yml +0 -0
  40. {flowjet-2.0.8 → flowjet-2.0.10}/docs/completion-design.md +0 -0
  41. {flowjet-2.0.8 → flowjet-2.0.10}/docs/drafts/README.md +0 -0
  42. {flowjet-2.0.8 → flowjet-2.0.10}/docs/drafts/api-spec.md +0 -0
  43. {flowjet-2.0.8 → flowjet-2.0.10}/docs/impl/IG-001-openai-compatible-server.md +0 -0
  44. {flowjet-2.0.8 → flowjet-2.0.10}/docs/impl/IG-002-isolated-thread-pool-runtime.md +0 -0
  45. {flowjet-2.0.8 → flowjet-2.0.10}/docs/impl/IG-003-production-isolation-hardening.md +0 -0
  46. {flowjet-2.0.8 → flowjet-2.0.10}/docs/impl/README.md +0 -0
  47. {flowjet-2.0.8 → flowjet-2.0.10}/docs/impl/TEST-COVERAGE-MATRIX.md +0 -0
  48. {flowjet-2.0.8 → flowjet-2.0.10}/docs/naming.md +0 -0
  49. {flowjet-2.0.8 → flowjet-2.0.10}/docs/server.md +0 -0
  50. {flowjet-2.0.8 → flowjet-2.0.10}/docs/specs/RFC-001-openai-compatible-api.md +0 -0
  51. {flowjet-2.0.8 → flowjet-2.0.10}/docs/specs/RFC-002-isolated-thread-pool-runtime.md +0 -0
  52. {flowjet-2.0.8 → flowjet-2.0.10}/docs/specs/RFC-003-production-isolation-hardening.md +0 -0
  53. {flowjet-2.0.8 → flowjet-2.0.10}/docs/specs/RFC-004-unified-package.md +0 -0
  54. {flowjet-2.0.8 → flowjet-2.0.10}/docs/specs/rfc-history.md +0 -0
  55. {flowjet-2.0.8 → flowjet-2.0.10}/docs/specs/rfc-index.md +0 -0
  56. {flowjet-2.0.8 → flowjet-2.0.10}/docs/specs/rfc-namings.md +0 -0
  57. {flowjet-2.0.8 → flowjet-2.0.10}/docs/upgrade.md +0 -0
  58. {flowjet-2.0.8 → flowjet-2.0.10}/examples/README.md +0 -0
  59. {flowjet-2.0.8 → flowjet-2.0.10}/nano.yml +0 -0
  60. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/__init__.py +0 -0
  61. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/__main__.py +0 -0
  62. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/README.md +0 -0
  63. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/gws-drive/SKILL.md +0 -0
  64. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/gws-drive-upload/SKILL.md +0 -0
  65. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/logo-generator-skill/.env.example +0 -0
  66. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/logo-generator-skill/README.md +0 -0
  67. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/logo-generator-skill/SKILL.md +0 -0
  68. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/logo-generator-skill/assets/background_library.html +0 -0
  69. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/logo-generator-skill/assets/showcase_template.html +0 -0
  70. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/logo-generator-skill/references/background_styles.md +0 -0
  71. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/logo-generator-skill/references/design_patterns.md +0 -0
  72. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/logo-generator-skill/references/webgl_backgrounds.md +0 -0
  73. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/logo-generator-skill/requirements.txt +0 -0
  74. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/logo-generator-skill/scripts/generate_showcase.py +0 -0
  75. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/logo-generator-skill/scripts/svg_to_png.py +0 -0
  76. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/SKILL.md +0 -0
  77. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/brainstorming/spec-document-reviewer-prompt.md +0 -0
  78. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/implementation/coding-plan-template.md +0 -0
  79. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/implementation/impl-guide-template.md +0 -0
  80. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/review/pr-review-template.md +0 -0
  81. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/review/review-checklist.md +0 -0
  82. {flowjet-2.0.8/docs/specs/templates → flowjet-2.0.10/src/flowjet/builtin_skills/platonic-coding/assets/specs}/rfc-template.md +0 -0
  83. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-architecture-design.md +0 -0
  84. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-conceptual-design.md +0 -0
  85. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-impl-interface-design.md +0 -0
  86. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-drafts-readme.md +0 -0
  87. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-impl-readme.md +0 -0
  88. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-platonic.yml +0 -0
  89. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-history.md +0 -0
  90. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-index.md +0 -0
  91. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-namings.md +0 -0
  92. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-standard.md +0 -0
  93. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/BRAINSTORM/brainstorm.md +0 -0
  94. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-code.md +0 -0
  95. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-create-guide.md +0 -0
  96. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-full.md +0 -0
  97. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-update-guide.md +0 -0
  98. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-validate-guide.md +0 -0
  99. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-plan-modular-specs.md +0 -0
  100. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-architecture.md +0 -0
  101. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-conceptual.md +0 -0
  102. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-impl-interface.md +0 -0
  103. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-scaffold.md +0 -0
  104. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-scan.md +0 -0
  105. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/REFERENCE.md +0 -0
  106. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/REVIEW/review-spec-compliance.md +0 -0
  107. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-check-compliance.md +0 -0
  108. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-check-taxonomy.md +0 -0
  109. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-history.md +0 -0
  110. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-index.md +0 -0
  111. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-namings.md +0 -0
  112. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-refine.md +0 -0
  113. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-validate-consistency.md +0 -0
  114. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-overview.md +0 -0
  115. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-1.md +0 -0
  116. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-2.md +0 -0
  117. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-3.md +0 -0
  118. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/SKILL.md +0 -0
  119. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/patterns/evidence-deep.json +0 -0
  120. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/patterns/evidence-first.json +0 -0
  121. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/patterns/idea-first.json +0 -0
  122. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/patterns/loop.json +0 -0
  123. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/patterns/paper-pipeline.json +0 -0
  124. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/patterns/rapid.json +0 -0
  125. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/patterns/rigor-first.json +0 -0
  126. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/patterns/stance-first.json +0 -0
  127. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/patterns/systematic-review.json +0 -0
  128. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/patterns/verify-loop.json +0 -0
  129. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/references/backends.md +0 -0
  130. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/references/handoff-ars-omr.md +0 -0
  131. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/references/handoff-omr-ars.md +0 -0
  132. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/references/routing.md +0 -0
  133. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/scripts/csljson_to_omr.py +0 -0
  134. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/scripts/papers_index_to_csljson.py +0 -0
  135. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/__init__.py +0 -0
  136. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/__main__.py +0 -0
  137. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/agent.py +0 -0
  138. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/completion/__init__.py +0 -0
  139. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/completion/context.py +0 -0
  140. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/completion/engine.py +0 -0
  141. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/doctor_cmd.py +0 -0
  142. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/doctor_fix_cmd.py +0 -0
  143. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/serve_cmd.py +0 -0
  144. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/setup_cmd.py +0 -0
  145. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/shell/__init__.py +0 -0
  146. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/shell/_fj.zsh +0 -0
  147. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/shell/fj.bash +0 -0
  148. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/stream.py +0 -0
  149. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/threads.py +0 -0
  150. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/tool_stream.py +0 -0
  151. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/__init__.py +0 -0
  152. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backends/__init__.py +0 -0
  153. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backends/direct.py +0 -0
  154. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backends/fake.py +0 -0
  155. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backends/isolation/admission.py +0 -0
  156. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backends/isolation/errors.py +0 -0
  157. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backends/isolation/mode_gate.py +0 -0
  158. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backends/isolation/pool.py +0 -0
  159. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backends/isolation/request.py +0 -0
  160. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/bridges/nano/__init__.py +0 -0
  161. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/bridges/nano/adapter.py +0 -0
  162. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/events.py +0 -0
  163. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/identity.py +0 -0
  164. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/modes.py +0 -0
  165. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/protocol.py +0 -0
  166. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/tool_results.py +0 -0
  167. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/home.py +0 -0
  168. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/__init__.py +0 -0
  169. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/acp/__init__.py +0 -0
  170. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/acp/routes.py +0 -0
  171. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/http/__init__.py +0 -0
  172. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/http/auth.py +0 -0
  173. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/openai_compat/__init__.py +0 -0
  174. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/openai_compat/errors.py +0 -0
  175. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/openai_compat/projection.py +0 -0
  176. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/openai_compat/routes.py +0 -0
  177. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/openai_compat/schemas.py +0 -0
  178. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/openai_compat/service.py +0 -0
  179. {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/openai_compat/store.py +0 -0
@@ -254,4 +254,6 @@ Temporary Items
254
254
 
255
255
  # Claude Code session-local
256
256
  .claude/
257
- .codebuddy/
257
+ .codebuddy/
258
+ # local wheel overrides for docker build (flowjet[boxlite] + local soothe)
259
+ wheels/*.whl
@@ -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.10] — 2026-10-14
14
+
15
+ ### Changed
16
+
17
+ - **Bump soothe and soothe-nano to the latest.** `soothe` `1.0.22 → 1.1.0`
18
+ and `soothe-nano` `1.3.3 → 1.4.2`. The version caps were lifted:
19
+ `soothe>=1.1.0,<2.0.0` and `soothe-nano>=1.4.2,<1.5.0`. The standalone
20
+ `soothe-sdk` package was removed in the new release; its modules merged
21
+ into `soothe` and `soothe-nano`. All FlowJet import paths were migrated:
22
+ `soothe_sdk.utils.serde` → `soothe_nano.utils.serde`,
23
+ `soothe_sdk.utils.formatting` → `soothe_nano.utils.formatting`,
24
+ `soothe_sdk.tools.metadata` → `soothe_nano.tools.metadata`, and
25
+ `soothe_sdk.display.message_processing` → `soothe.display.message_processing`.
26
+ The graceful try/except fallbacks in `progress.py` and `tool_stream.py`
27
+ now resolve to the real implementations instead of falling through to
28
+ fallbacks.
29
+
30
+ ## [2.0.9] — 2026-09-24
31
+
32
+ ### Fixed
33
+
34
+ - **Suppress noisy third-party warnings at startup.** `requests` emits
35
+ `RequestsDependencyWarning` when the installed `urllib3` / `chardet` /
36
+ `charset_normalizer` versions fall outside its supported range, and
37
+ `soothe`'s `TypeSafeClassifier` triggers LangChain's `LangChainBetaWarning`.
38
+ Both are version-mismatch or beta-API notices the user cannot act on, so
39
+ they are now silenced at the earliest point of each entry point. A new
40
+ `core/_warnings.py` module exposes `suppress_known_warnings()`, called from
41
+ both the CLI (`cli.py`) and server (`server/__main__.py`) `main()` before
42
+ the heavy agent stack is imported.
43
+
13
44
  ## [2.0.8] — 2026-09-24
14
45
 
15
46
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: flowjet
3
- Version: 2.0.8
3
+ Version: 2.0.10
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.4.0,>=1.3.0
26
- Requires-Dist: soothe<2.0.0,>=1.0.14
25
+ Requires-Dist: soothe-nano<1.5.0,>=1.4.2
26
+ Requires-Dist: soothe<2.0.0,>=1.1.0
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'
@@ -35,10 +35,12 @@ Requires-Dist: ruff>=0.12.0; extra == 'dev'
35
35
  Requires-Dist: twine>=5.0.0; extra == 'dev'
36
36
  Provides-Extra: server
37
37
  Requires-Dist: agent-client-protocol[http]<1.0.0,>=0.12.1; extra == 'server'
38
+ Requires-Dist: boxlite>=0.1.0; extra == 'server'
38
39
  Requires-Dist: fastapi>=0.115.0; extra == 'server'
39
40
  Requires-Dist: langchain-core>=0.3; extra == 'server'
40
41
  Requires-Dist: pydantic-settings>=2.0; extra == 'server'
41
42
  Requires-Dist: pydantic>=2.0; extra == 'server'
43
+ Requires-Dist: s3fs>=2024.6; extra == 'server'
42
44
  Requires-Dist: uvicorn[standard]>=0.32.0; extra == 'server'
43
45
  Description-Content-Type: text/markdown
44
46
 
@@ -54,7 +54,7 @@ Services bound to localhost only. PostgreSQL uses default credentials (postgres/
54
54
 
55
55
  ## Config
56
56
 
57
- `nano.yml` uses the DashScope (OpenAI-compatible) provider with `${ENV_VAR}` substitution and PostgreSQL + pgvector persistence. It is mounted over the image's baked default (`/var/lib/soothe/config/nano.yml`), which uses SQLite for local development — the mount switches the whole runtime to Postgres mode for the container stack.
57
+ `nano.yml` uses the DashScope (OpenAI-compatible) provider with `${ENV_VAR}` substitution and PostgreSQL + pgvector persistence. It is mounted over the image's baked default (`/flowjet/config/nano.yml`), which uses SQLite for local development — the mount switches the whole runtime to Postgres mode for the container stack.
58
58
 
59
59
  ## Security
60
60
 
@@ -3,7 +3,7 @@
3
3
  # Mirrors ../soothe/deploy/nano.yml structure; keeps the DashScope provider
4
4
  # from config/nano.yml (flowjet-server default).
5
5
  #
6
- # Mounted at /var/lib/soothe/config/nano.yml (default agent path).
6
+ # Mounted at /flowjet/config/nano.yml (default agent path).
7
7
  # Uses environment variable substitution (${ENV_VAR}).
8
8
  #
9
9
  # Required env vars (set in deploy/.env):
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "flowjet"
7
- version = "2.0.8"
7
+ version = "2.0.10"
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" }
@@ -25,14 +25,10 @@ classifiers = [
25
25
  "Topic :: Scientific/Engineering :: Artificial Intelligence",
26
26
  "Topic :: Software Development :: Libraries :: Python Modules",
27
27
  ]
28
- # The CLI stays a single-dependency wheel. Everything the HTTP service needs
29
- # lives in the `server` extra, and `flowjet.cli` never imports it at module
30
- # scope (see tests/unit/test_cli_no_server_deps.py).
28
+ # Single-dependency CLI wheel; HTTP service deps live in the `server` extra.
31
29
  dependencies = [
32
- # Default CLI runtime (FLOWJET_BACKEND=soothe). soothe is a host layer over
33
- # soothe-nano, so nano stays pinned explicitly: soothe only caps it at <2.
34
- "soothe>=1.0.14,<2.0.0",
35
- "soothe-nano>=1.3.0,<1.4.0",
30
+ "soothe>=1.1.0,<2.0.0",
31
+ "soothe-nano>=1.4.2,<1.5.0",
36
32
  ]
37
33
 
38
34
  [project.optional-dependencies]
@@ -43,6 +39,10 @@ server = [
43
39
  "pydantic-settings>=2.0",
44
40
  "agent-client-protocol[http]>=0.12.1,<1.0.0",
45
41
  "langchain-core>=0.3",
42
+ # S3/MinIO workspace sync backend
43
+ "s3fs>=2024.6",
44
+ # microVM-per-workspace session isolation (https://github.com/boxlite-ai/boxlite)
45
+ "boxlite>=0.1.0",
46
46
  ]
47
47
  dev = [
48
48
  "pytest>=8.0.0",
@@ -62,14 +62,11 @@ dev = [
62
62
 
63
63
  [tool.uv]
64
64
  default-groups = ["dev"]
65
- # cryptography 46+ ships macOS wheels for arm64 only; Intel Macs must build from
66
- # source (Rust), which is slow and looks like a hang during `uv sync`.
65
+ # cryptography 46+ lacks x86_64 macOS wheels; Intel Macs would build from source.
67
66
  constraint-dependencies = [
68
67
  "cryptography>=45,<46; platform_machine == 'x86_64' and sys_platform == 'darwin'",
69
68
  ]
70
69
 
71
- # soothe-nano is published on PyPI (>=0.9.9); resolve it from the default index.
72
-
73
70
  [project.scripts]
74
71
  flowjet = "flowjet.cli.cli:main"
75
72
  fj = "flowjet.cli.cli:main"
@@ -87,16 +84,13 @@ packages = ["src/flowjet"]
87
84
  [tool.hatch.build.targets.sdist]
88
85
  include = ["src/flowjet/", "nano.yml", "README.md", "LICENSE", "docs/", "CHANGELOG.md"]
89
86
 
90
- # Non-Python assets (shell completions, builtin_skills/) are shipped automatically
91
- # because they live inside the `src/flowjet` package, which hatchling includes
92
- # recursively. Do NOT re-list them in force-include — that adds the same file
93
- # twice and breaks `uv build` with "A second file is being added ... at the
94
- # same path".
87
+ # Non-Python assets inside `src/flowjet` ship automatically; re-listing them in
88
+ # force-include duplicates files and breaks `uv build`.
95
89
 
96
90
  [tool.ruff]
97
91
  line-length = 100
98
92
  target-version = "py311"
99
- # AgentSkills scripts are third-party / skill-local style; keep them out of package lint.
93
+ # Third-party / skill-local scripts; excluded from package lint.
100
94
  extend-exclude = ["src/flowjet/builtin_skills"]
101
95
 
102
96
  [tool.ruff.lint]
@@ -112,8 +106,7 @@ warn_return_any = true
112
106
  warn_unused_configs = true
113
107
  disallow_untyped_defs = true
114
108
 
115
- # The moved server/bridge code is Any-heavy (nano and langchain objects); it is
116
- # checked loosely until it is annotated. CI has no mypy gate today.
109
+ # Any-heavy moved code (nano/langchain objects); loosened until annotated.
117
110
  [[tool.mypy.overrides]]
118
111
  module = ["flowjet.server.*", "flowjet.core.bridges.*"]
119
112
  disallow_untyped_defs = false
@@ -717,6 +717,11 @@ def main_follow(argv: list[str] | None = None) -> int:
717
717
 
718
718
 
719
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()
720
725
  try:
721
726
  # Parse first so ``-V`` / ``-h`` / ``--help`` exit before we touch the
722
727
  # logging stack (and the heavy ``flowjet.cli.agent`` import it used to
@@ -308,7 +308,7 @@ def _compact(value: Any) -> str:
308
308
 
309
309
  def _abbrev_path(path: str) -> str:
310
310
  try:
311
- from soothe_sdk.utils.formatting import convert_and_abbreviate_path
311
+ from soothe_nano.utils.formatting import convert_and_abbreviate_path
312
312
 
313
313
  return convert_and_abbreviate_path(path)
314
314
  except Exception:
@@ -320,7 +320,7 @@ def _abbrev_path(path: str) -> str:
320
320
 
321
321
  def _tool_meta(name: str) -> Any | None:
322
322
  try:
323
- from soothe_sdk.tools.metadata import get_tool_meta
323
+ from soothe_nano.tools.metadata import get_tool_meta
324
324
 
325
325
  return get_tool_meta(name)
326
326
  except Exception:
@@ -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
@@ -15,15 +15,17 @@ FlowJet can build its agent two ways:
15
15
 
16
16
  The two surfaces switch **independently**, because they do not share the same
17
17
  constraints: the CLI builds one agent per invocation, while the server needs a
18
- dual-mode agent (AGENT + ASK graphs selected per request) — an API soothe does
19
- not expose today. Hence the defaults differ:
18
+ dual-mode agent (AGENT + ASK graphs selected per request). The server now uses
19
+ ``ModeRoutedSootheAgent`` — one host agent per mode, lazily built — to provide
20
+ dual-mode routing on the soothe backend. Hence both surfaces default to
21
+ ``soothe``:
20
22
 
21
23
  * ``FLOWJET_BACKEND`` (CLI) — default ``soothe``.
22
- * ``FLOWJET_SERVER_BACKEND`` (HTTP/ACP) — default ``nano``.
24
+ * ``FLOWJET_SERVER_BACKEND`` (HTTP/ACP) — default ``soothe``.
23
25
 
24
26
  Both accept ``soothe`` or ``nano`` (case-insensitive); anything else is a hard
25
27
  error rather than a silent fallback, so a typo can never quietly run the wrong
26
- runtime.
28
+ runtime. Set ``FLOWJET_SERVER_BACKEND=nano`` to fall back to the nano runtime.
27
29
  """
28
30
 
29
31
  from __future__ import annotations
@@ -47,9 +49,10 @@ class Backend(StrEnum):
47
49
  #: CLI builds one agent per invocation; the host runtime is the default there.
48
50
  DEFAULT_CLI_BACKEND = Backend.SOOTHE
49
51
 
50
- #: The server needs per-request mode switching (dual-mode agent), which soothe
51
- #: does not expose; it stays on nano until host dual-mode exists.
52
- DEFAULT_SERVER_BACKEND = Backend.NANO
52
+ #: Both surfaces default to the host runtime (``soothe``). The server uses
53
+ #: ``ModeRoutedSootheAgent`` for per-request mode switching (one host agent per
54
+ #: mode, lazily built). Set ``FLOWJET_SERVER_BACKEND=nano`` to fall back to nano.
55
+ DEFAULT_SERVER_BACKEND = Backend.SOOTHE
53
56
 
54
57
  _BACKENDS: dict[str, Backend] = {b.value: b for b in Backend}
55
58
 
@@ -1,24 +1,36 @@
1
- """Isolated thread-pool runtime (RFC-002 / RFC-003)."""
1
+ """Isolated runtime: thread-pool and boxlite microVM isolation."""
2
2
 
3
3
  from flowjet.core.backends.isolation.adapter import AgentAdapter, FakeAgentAdapter
4
4
  from flowjet.core.backends.isolation.admission import SessionAdmission
5
5
  from flowjet.core.backends.isolation.backend import IsolatingRuntimeBackend
6
+ from flowjet.core.backends.isolation.boxlite_vm_pool import (
7
+ BoxLiteVMIsolationPool,
8
+ BoxLiteVMSettings,
9
+ )
6
10
  from flowjet.core.backends.isolation.errors import IsolationError
11
+ from flowjet.core.backends.isolation.isolation_pool import IsolationPool
7
12
  from flowjet.core.backends.isolation.mode_gate import InteractionModeGate
8
13
  from flowjet.core.backends.isolation.pool import PoolMetrics, PoolSettings, ThreadPool
9
14
  from flowjet.core.backends.isolation.request import IsolatedRunRequest
10
- from flowjet.core.backends.isolation.workspace import WorkspaceResolver
15
+ from flowjet.core.backends.isolation.workspace import (
16
+ SyncAwareWorkspaceResolver,
17
+ WorkspaceResolver,
18
+ )
11
19
 
12
20
  __all__ = [
13
21
  "AgentAdapter",
22
+ "BoxLiteVMIsolationPool",
23
+ "BoxLiteVMSettings",
14
24
  "FakeAgentAdapter",
15
25
  "IsolatedRunRequest",
16
26
  "IsolatingRuntimeBackend",
17
27
  "InteractionModeGate",
18
28
  "IsolationError",
29
+ "IsolationPool",
19
30
  "PoolMetrics",
20
31
  "PoolSettings",
21
32
  "SessionAdmission",
33
+ "SyncAwareWorkspaceResolver",
22
34
  "ThreadPool",
23
35
  "WorkspaceResolver",
24
36
  ]
@@ -12,6 +12,8 @@ from flowjet.core.events import (
12
12
  RunCompleted,
13
13
  RunStarted,
14
14
  RuntimeEvent,
15
+ ToolCompleted,
16
+ ToolStarted,
15
17
  UsageInfo,
16
18
  )
17
19
 
@@ -53,6 +55,14 @@ class FakeAgentAdapter:
53
55
  yield Progress(stage="Working", message="Thinking…")
54
56
  if self.delay_s > 0:
55
57
  await asyncio.sleep(self.delay_s)
58
+
59
+ # Emit tool events when the caller requests them via metadata
60
+ # (mirrors FakeRuntimeBackend's emit_tools handling).
61
+ meta = req.metadata or {}
62
+ if meta.get("emit_tools"):
63
+ yield ToolStarted(tool="search", call_id="call_1")
64
+ yield ToolCompleted(tool="search", ok=True, call_id="call_1", duration_ms=12)
65
+
56
66
  # Write a marker file so concurrency tests can assert workspace isolation.
57
67
  marker = req.workspace / "last_input.txt"
58
68
  marker.write_text(req.input_text, encoding="utf-8")
@@ -1,4 +1,11 @@
1
- """RuntimeBackend that admits, resolves workspaces, and submits to ThreadPool."""
1
+ """RuntimeBackend that admits, resolves workspaces, and submits to an isolation pool.
2
+
3
+ Phase 1–2 use :class:`~flowjet.core.backends.isolation.pool.ThreadPool`.
4
+ Phase 3 adds :class:`~flowjet.core.backends.isolation.boxlite_vm_pool.BoxLiteVMIsolationPool`
5
+ for microVM-per-workspace session isolation via the external boxlite runtime.
6
+ Both implement the
7
+ :class:`~flowjet.core.backends.isolation.isolation_pool.IsolationPool` protocol.
8
+ """
2
9
 
3
10
  from __future__ import annotations
4
11
 
@@ -17,7 +24,17 @@ from flowjet.core.events import ModelInfo, RunRequest, RuntimeEvent
17
24
 
18
25
 
19
26
  class IsolatingRuntimeBackend:
20
- """Pool-backed RuntimeBackend for the soothe-nano AgentAdapter."""
27
+ """Pool-backed RuntimeBackend for the soothe/nano AgentAdapter.
28
+
29
+ The workspace resolver is injectable (Phase 2): callers can pass a
30
+ :class:`SyncAwareWorkspaceResolver` to enable fsspec workspace sync.
31
+ The default is the local-only :class:`WorkspaceResolver` (Phase 1).
32
+
33
+ The isolation pool is injectable (Phase 3): callers can pass a
34
+ :class:`~flowjet.core.backends.isolation.boxlite_vm_pool.BoxLiteVMIsolationPool`
35
+ to enable microVM-per-workspace session isolation. The default is the
36
+ in-process :class:`ThreadPool` (Phase 1/2).
37
+ """
21
38
 
22
39
  def __init__(
23
40
  self,
@@ -27,13 +44,14 @@ class IsolatingRuntimeBackend:
27
44
  pool_settings: PoolSettings | None = None,
28
45
  home: Path | str | None = None,
29
46
  admission: SessionAdmission | None = None,
30
- pool: ThreadPool | None = None,
47
+ pool: Any | None = None,
31
48
  allow_external_workspace: bool = False,
32
49
  mode_gate: InteractionModeGate | None = None,
50
+ workspace_resolver: WorkspaceResolver | None = None,
33
51
  ) -> None:
34
52
  self._models = models or ["default"]
35
53
  home_path = Path(home).expanduser() if home else Path.home() / ".flowjet"
36
- self._workspaces = WorkspaceResolver(
54
+ self._workspaces = workspace_resolver or WorkspaceResolver(
37
55
  home_path, allow_external_workspace=allow_external_workspace
38
56
  )
39
57
  self._admission = admission or SessionAdmission()
@@ -42,7 +60,7 @@ class IsolatingRuntimeBackend:
42
60
  self._started = False
43
61
 
44
62
  @property
45
- def pool(self) -> ThreadPool:
63
+ def pool(self) -> Any:
46
64
  return self._pool
47
65
 
48
66
  @property
@@ -90,9 +108,18 @@ class IsolatingRuntimeBackend:
90
108
  metadata=metadata,
91
109
  )
92
110
  async with self._admission.admit(session):
93
- async for event in self._pool.submit(isolated):
94
- yield event
111
+ await self._workspaces.open_session(session, workspace)
112
+ try:
113
+ async for event in self._pool.submit(isolated):
114
+ yield event
115
+ finally:
116
+ await self._workspaces.close_session(session, workspace)
95
117
 
96
118
  async def shutdown(self) -> None:
97
119
  await self._pool.shutdown()
120
+ shutdown_ws = getattr(self._workspaces, "shutdown", None)
121
+ if shutdown_ws is not None:
122
+ result = shutdown_ws()
123
+ if hasattr(result, "__await__"):
124
+ await result
98
125
  self._started = False
@@ -0,0 +1,161 @@
1
+ """JSON-lines protocol for boxlite container workers (Phase 3).
2
+
3
+ Serializes :class:`IsolatedRunRequest` to JSON for transmission over the
4
+ worker's stdin, and deserializes JSON event frames from the worker's stdout
5
+ back into :data:`RuntimeEvent` objects.
6
+
7
+ Wire format (one JSON object per line)::
8
+
9
+ {"type": "request", ...} ← sent to worker stdin
10
+ {"type": "event", "event_type": "RunStarted", ...}
11
+ {"type": "done"}
12
+ {"type": "error", "message": "..."}
13
+ {"type": "cancelled"}
14
+ {"type": "ready"} ← worker is ready for next request
15
+
16
+ No external dependencies — only stdlib ``json`` and the flowjet event
17
+ dataclasses. This module is imported both by the pool (host side) and by
18
+ the worker script (container side).
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ from pathlib import Path
25
+ from typing import Any
26
+
27
+ from flowjet.core.backends.isolation.request import IsolatedRunRequest
28
+ from flowjet.core.events import (
29
+ InterruptWaiting,
30
+ Narration,
31
+ OutputTextDelta,
32
+ Progress,
33
+ RunCompleted,
34
+ RunFailed,
35
+ RunStarted,
36
+ RuntimeEvent,
37
+ ToolCompleted,
38
+ ToolStarted,
39
+ UsageInfo,
40
+ )
41
+
42
+ # Tag used in JSON frames to identify the event class.
43
+ _EVENT_TYPE_TAG = "event_type"
44
+
45
+ # Mapping from event type tag → constructor (for deserialization).
46
+ _EVENT_REGISTRY: dict[str, type] = {
47
+ cls.__name__: cls
48
+ for cls in (
49
+ RunStarted,
50
+ Progress,
51
+ ToolStarted,
52
+ ToolCompleted,
53
+ Narration,
54
+ OutputTextDelta,
55
+ InterruptWaiting,
56
+ RunCompleted,
57
+ RunFailed,
58
+ )
59
+ }
60
+
61
+
62
+ # ---------------------------------------------------------------------------
63
+ # Request serialization (host → worker)
64
+ # ---------------------------------------------------------------------------
65
+
66
+
67
+ def encode_request(req: IsolatedRunRequest) -> str:
68
+ """Serialize an :class:`IsolatedRunRequest` to a JSON-lines string."""
69
+ payload: dict[str, Any] = {
70
+ "type": "request",
71
+ "request_id": req.request_id,
72
+ "run_id": req.run_id,
73
+ "session": req.session,
74
+ "input_text": req.input_text,
75
+ "model": req.model,
76
+ "workspace": str(req.workspace),
77
+ "thread_id": req.thread_id,
78
+ "metadata": req.metadata,
79
+ }
80
+ return json.dumps(payload, ensure_ascii=False)
81
+
82
+
83
+ # ---------------------------------------------------------------------------
84
+ # Event serialization (worker → host)
85
+ # ---------------------------------------------------------------------------
86
+
87
+
88
+ def encode_event(event: RuntimeEvent) -> str:
89
+ """Serialize a :data:`RuntimeEvent` to a JSON-lines string."""
90
+ from dataclasses import asdict
91
+
92
+ data = asdict(event) # type: ignore[arg-type]
93
+ data["type"] = "event"
94
+ data[_EVENT_TYPE_TAG] = type(event).__name__
95
+ return json.dumps(data, ensure_ascii=False)
96
+
97
+
98
+ def decode_event(frame: dict[str, Any]) -> RuntimeEvent:
99
+ """Deserialize a JSON frame dict into a :data:`RuntimeEvent`.
100
+
101
+ Raises :class:`ValueError` for unknown event types.
102
+ """
103
+ event_type = frame.get(_EVENT_TYPE_TAG)
104
+ if event_type is None:
105
+ raise ValueError(f"frame missing {_EVENT_TYPE_TAG!r}: {frame!r}")
106
+ cls = _EVENT_REGISTRY.get(event_type)
107
+ if cls is None:
108
+ raise ValueError(f"unknown event type: {event_type!r}")
109
+ # Strip protocol-level keys before constructing the dataclass.
110
+ kwargs = {k: v for k, v in frame.items() if k not in ("type", _EVENT_TYPE_TAG)}
111
+ # Reconstruct nested dataclass fields (e.g. RunCompleted.usage → UsageInfo).
112
+ kwargs = _reconstruct_nested(kwargs)
113
+ return cls(**kwargs) # type: ignore[call-arg]
114
+
115
+
116
+ def _reconstruct_nested(kwargs: dict[str, Any]) -> dict[str, Any]:
117
+ """Convert dict values back into nested dataclass instances where needed.
118
+
119
+ :func:`dataclasses.asdict` recursively converts nested dataclasses to
120
+ plain dicts. This reverses that for known fields whose constructors
121
+ expect a dataclass, not a dict.
122
+ """
123
+ if "usage" in kwargs and isinstance(kwargs["usage"], dict):
124
+ kwargs["usage"] = UsageInfo(**kwargs["usage"])
125
+ return kwargs
126
+
127
+
128
+ # ---------------------------------------------------------------------------
129
+ # Control frame helpers
130
+ # ---------------------------------------------------------------------------
131
+
132
+
133
+ def make_control(frame_type: str, **fields: Any) -> str:
134
+ """Build a JSON-lines control frame (``done``, ``error``, etc.)."""
135
+ payload: dict[str, Any] = {"type": frame_type}
136
+ payload.update(fields)
137
+ return json.dumps(payload, ensure_ascii=False)
138
+
139
+
140
+ def parse_frame(line: str) -> dict[str, Any]:
141
+ """Parse one JSON-lines frame from a worker's stdout."""
142
+ return json.loads(line)
143
+
144
+
145
+ # ---------------------------------------------------------------------------
146
+ # Request deserialization (worker side)
147
+ # ---------------------------------------------------------------------------
148
+
149
+
150
+ def decode_request(frame: dict[str, Any]) -> IsolatedRunRequest:
151
+ """Deserialize a JSON frame dict into an :class:`IsolatedRunRequest`."""
152
+ return IsolatedRunRequest(
153
+ run_id=frame["run_id"],
154
+ session=frame["session"],
155
+ input_text=frame["input_text"],
156
+ model=frame["model"],
157
+ workspace=Path(frame["workspace"]),
158
+ thread_id=frame.get("thread_id"),
159
+ metadata=frame.get("metadata"),
160
+ request_id=frame.get("request_id"),
161
+ )