flowjet 2.1.0__tar.gz → 2.1.2__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 (174) hide show
  1. {flowjet-2.1.0 → flowjet-2.1.2}/CHANGELOG.md +32 -0
  2. {flowjet-2.1.0 → flowjet-2.1.2}/PKG-INFO +2 -3
  3. {flowjet-2.1.0 → flowjet-2.1.2}/docs/specs/RFC-005-arcbox-tool-sandbox.md +7 -2
  4. {flowjet-2.1.0 → flowjet-2.1.2}/pyproject.toml +2 -5
  5. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/logo-generator-skill/SKILL.md +1 -1
  6. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/cli/stream.py +17 -4
  7. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/runtime/isolation/workspace.py +109 -20
  8. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/sandbox/arbox.py +40 -9
  9. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/server/acp/agent.py +18 -0
  10. {flowjet-2.1.0 → flowjet-2.1.2}/.gitignore +0 -0
  11. {flowjet-2.1.0 → flowjet-2.1.2}/LICENSE +0 -0
  12. {flowjet-2.1.0 → flowjet-2.1.2}/README.md +0 -0
  13. {flowjet-2.1.0 → flowjet-2.1.2}/config/nano-local.yml +0 -0
  14. {flowjet-2.1.0 → flowjet-2.1.2}/config/nano-sandbox-soothe.yml +0 -0
  15. {flowjet-2.1.0 → flowjet-2.1.2}/config/nano.yml +0 -0
  16. {flowjet-2.1.0 → flowjet-2.1.2}/deploy/README.md +0 -0
  17. {flowjet-2.1.0 → flowjet-2.1.2}/docs/completion-design.md +0 -0
  18. {flowjet-2.1.0 → flowjet-2.1.2}/docs/impl/IG-001-openai-compatible-server.md +0 -0
  19. {flowjet-2.1.0 → flowjet-2.1.2}/docs/impl/IG-002-isolated-thread-pool-runtime.md +0 -0
  20. {flowjet-2.1.0 → flowjet-2.1.2}/docs/impl/IG-003-production-isolation-hardening.md +0 -0
  21. {flowjet-2.1.0 → flowjet-2.1.2}/docs/specs/RFC-001-openai-compatible-api.md +0 -0
  22. {flowjet-2.1.0 → flowjet-2.1.2}/docs/specs/RFC-002-isolated-thread-pool-runtime.md +0 -0
  23. {flowjet-2.1.0 → flowjet-2.1.2}/docs/specs/RFC-003-production-isolation-hardening.md +0 -0
  24. {flowjet-2.1.0 → flowjet-2.1.2}/docs/specs/RFC-004-unified-package.md +0 -0
  25. {flowjet-2.1.0 → flowjet-2.1.2}/docs/specs/rfc-history.md +0 -0
  26. {flowjet-2.1.0 → flowjet-2.1.2}/docs/specs/rfc-index.md +0 -0
  27. {flowjet-2.1.0 → flowjet-2.1.2}/docs/specs/rfc-namings.md +0 -0
  28. {flowjet-2.1.0 → flowjet-2.1.2}/examples/README.md +0 -0
  29. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/__init__.py +0 -0
  30. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/__main__.py +0 -0
  31. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/README.md +0 -0
  32. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/gws-drive/SKILL.md +0 -0
  33. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/gws-drive-upload/SKILL.md +0 -0
  34. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/logo-generator-skill/.env.example +0 -0
  35. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/logo-generator-skill/README.md +0 -0
  36. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/logo-generator-skill/assets/background_library.html +0 -0
  37. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/logo-generator-skill/assets/showcase_template.html +0 -0
  38. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/logo-generator-skill/references/background_styles.md +0 -0
  39. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/logo-generator-skill/references/design_patterns.md +0 -0
  40. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/logo-generator-skill/references/webgl_backgrounds.md +0 -0
  41. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/logo-generator-skill/requirements.txt +0 -0
  42. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/logo-generator-skill/scripts/generate_showcase.py +0 -0
  43. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/logo-generator-skill/scripts/svg_to_png.py +0 -0
  44. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/SKILL.md +0 -0
  45. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/assets/brainstorming/spec-document-reviewer-prompt.md +0 -0
  46. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/assets/implementation/coding-plan-template.md +0 -0
  47. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/assets/implementation/impl-guide-template.md +0 -0
  48. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/assets/review/pr-review-template.md +0 -0
  49. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/assets/review/review-checklist.md +0 -0
  50. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/assets/specs/rfc-template.md +0 -0
  51. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-architecture-design.md +0 -0
  52. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-conceptual-design.md +0 -0
  53. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-impl-interface-design.md +0 -0
  54. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-drafts-readme.md +0 -0
  55. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-impl-readme.md +0 -0
  56. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-platonic.yml +0 -0
  57. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-history.md +0 -0
  58. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-index.md +0 -0
  59. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-namings.md +0 -0
  60. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-standard.md +0 -0
  61. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/BRAINSTORM/brainstorm.md +0 -0
  62. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-code.md +0 -0
  63. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-create-guide.md +0 -0
  64. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-full.md +0 -0
  65. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-update-guide.md +0 -0
  66. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-validate-guide.md +0 -0
  67. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-plan-modular-specs.md +0 -0
  68. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-architecture.md +0 -0
  69. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-conceptual.md +0 -0
  70. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-impl-interface.md +0 -0
  71. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-scaffold.md +0 -0
  72. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-scan.md +0 -0
  73. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/REFERENCE.md +0 -0
  74. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/REVIEW/review-spec-compliance.md +0 -0
  75. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-check-compliance.md +0 -0
  76. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-check-taxonomy.md +0 -0
  77. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-history.md +0 -0
  78. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-index.md +0 -0
  79. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-namings.md +0 -0
  80. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-refine.md +0 -0
  81. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-validate-consistency.md +0 -0
  82. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-overview.md +0 -0
  83. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-1.md +0 -0
  84. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-2.md +0 -0
  85. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-3.md +0 -0
  86. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/research-bootstrap/SKILL.md +0 -0
  87. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/research-bootstrap/patterns/evidence-deep.json +0 -0
  88. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/research-bootstrap/patterns/evidence-first.json +0 -0
  89. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/research-bootstrap/patterns/idea-first.json +0 -0
  90. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/research-bootstrap/patterns/loop.json +0 -0
  91. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/research-bootstrap/patterns/paper-pipeline.json +0 -0
  92. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/research-bootstrap/patterns/rapid.json +0 -0
  93. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/research-bootstrap/patterns/rigor-first.json +0 -0
  94. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/research-bootstrap/patterns/stance-first.json +0 -0
  95. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/research-bootstrap/patterns/systematic-review.json +0 -0
  96. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/research-bootstrap/patterns/verify-loop.json +0 -0
  97. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/research-bootstrap/references/backends.md +0 -0
  98. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/research-bootstrap/references/handoff-ars-omr.md +0 -0
  99. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/research-bootstrap/references/handoff-omr-ars.md +0 -0
  100. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/research-bootstrap/references/routing.md +0 -0
  101. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/research-bootstrap/scripts/csljson_to_omr.py +0 -0
  102. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/builtin_skills/research-bootstrap/scripts/papers_index_to_csljson.py +0 -0
  103. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/cli/__init__.py +0 -0
  104. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/cli/__main__.py +0 -0
  105. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/cli/agent.py +0 -0
  106. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/cli/cli.py +0 -0
  107. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/cli/completion/__init__.py +0 -0
  108. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/cli/completion/context.py +0 -0
  109. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/cli/completion/engine.py +0 -0
  110. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/cli/doctor_cmd.py +0 -0
  111. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/cli/doctor_fix_cmd.py +0 -0
  112. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/cli/progress.py +0 -0
  113. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/cli/serve_cmd.py +0 -0
  114. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/cli/setup_cmd.py +0 -0
  115. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/cli/shell/__init__.py +0 -0
  116. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/cli/shell/_fj.zsh +0 -0
  117. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/cli/shell/fj.bash +0 -0
  118. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/cli/threads.py +0 -0
  119. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/cli/tool_stream.py +0 -0
  120. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/__init__.py +0 -0
  121. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/_warnings.py +0 -0
  122. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/agent/__init__.py +0 -0
  123. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/agent/config.py +0 -0
  124. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/agent/factory.py +0 -0
  125. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/agent/skills.py +0 -0
  126. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/agent/wiring.py +0 -0
  127. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/agent/workspace.py +0 -0
  128. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/backend_config.py +0 -0
  129. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/bridges/base.py +0 -0
  130. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/bridges/nano/__init__.py +0 -0
  131. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/bridges/nano/adapter.py +0 -0
  132. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/bridges/nano/backend.py +0 -0
  133. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/bridges/nano/mapping.py +0 -0
  134. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/bridges/soothe/__init__.py +0 -0
  135. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/bridges/soothe/adapter.py +0 -0
  136. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/bridges/soothe/backend.py +0 -0
  137. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/events.py +0 -0
  138. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/identity.py +0 -0
  139. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/modes.py +0 -0
  140. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/protocol.py +0 -0
  141. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/runtime/__init__.py +0 -0
  142. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/runtime/direct.py +0 -0
  143. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/runtime/fake.py +0 -0
  144. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/runtime/isolation/__init__.py +0 -0
  145. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/runtime/isolation/adapter.py +0 -0
  146. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/runtime/isolation/admission.py +0 -0
  147. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/runtime/isolation/backend.py +0 -0
  148. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/runtime/isolation/errors.py +0 -0
  149. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/runtime/isolation/mode_gate.py +0 -0
  150. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/runtime/isolation/pool.py +0 -0
  151. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/runtime/isolation/request.py +0 -0
  152. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/sandbox/__init__.py +0 -0
  153. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/sandbox/resolver.py +0 -0
  154. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/sandbox/settings.py +0 -0
  155. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/streaming/__init__.py +0 -0
  156. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/streaming/presentation.py +0 -0
  157. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/streaming/tool_results.py +0 -0
  158. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/core/streaming/tool_stream.py +0 -0
  159. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/home.py +0 -0
  160. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/server/__init__.py +0 -0
  161. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/server/__main__.py +0 -0
  162. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/server/acp/__init__.py +0 -0
  163. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/server/acp/routes.py +0 -0
  164. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/server/config.py +0 -0
  165. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/server/http/__init__.py +0 -0
  166. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/server/http/app.py +0 -0
  167. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/server/http/auth.py +0 -0
  168. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/server/openai_compat/__init__.py +0 -0
  169. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/server/openai_compat/errors.py +0 -0
  170. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/server/openai_compat/projection.py +0 -0
  171. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/server/openai_compat/routes.py +0 -0
  172. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/server/openai_compat/schemas.py +0 -0
  173. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/server/openai_compat/service.py +0 -0
  174. {flowjet-2.1.0 → flowjet-2.1.2}/src/flowjet/server/openai_compat/store.py +0 -0
@@ -10,6 +10,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [2.1.2] — 2026-10-21
14
+
15
+ ### Changed
16
+
17
+ - **Bump `soothe` to 1.2.1.** The runtime dependency floor moves from
18
+ `>=1.2.0` to `>=1.2.1,<2.0.0` to track the upstream fix release. No flowjet
19
+ API or behavior changes; the locked dependency set is otherwise unchanged.
20
+
21
+ ## [2.1.1] — 2026-10-21
22
+
23
+ ### Fixed
24
+
25
+ - **ACP clarification turns no longer end empty.** When the agent paused with
26
+ `ask_user` questions over ACP, the questions were only surfaced as an
27
+ intermediate agent thought — but hosts accumulate message chunks as the
28
+ turn's reply, so a clarification turn ended with no content at all. The ACP
29
+ agent now also emits the questions block as a message chunk (rendered via
30
+ `format_questions_block`), so the user sees the questions in the host's reply
31
+ surface and answers there.
32
+
33
+ - **`format_questions_block` supports non-CLI hosts.** The resume footer
34
+ ("Resume with: fjf -t <thread> <your answer>") was hardcoded for the CLI.
35
+ ACP and other non-CLI hosts now pass `resume_hint=""` to omit the
36
+ CLI-specific footer; the user answers in the host's own input surface.
37
+ The CLI path is unchanged (footer still shown by default).
38
+
39
+ - **Drop the direct `arcbox` dependency from the `server` extra.** The arcbox
40
+ SDK ships transitively via soothe-nano (a dependency of soothe), so the
41
+ explicit `arcbox>=0.1.0` pin in `pyproject.toml` was redundant and could
42
+ drift out of sync. The sandbox import error message is updated to point at
43
+ the transitive path instead of instructing `pip install arcbox`.
44
+
13
45
  ## [2.1.0] — 2026-10-18
14
46
 
15
47
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: flowjet
3
- Version: 2.1.0
3
+ Version: 2.1.2
4
4
  Summary: FlowJet — agent CLI (fj) and server (ACP + OpenAI-compatible HTTP) powered by Soothe
5
5
  Project-URL: Homepage, https://github.com/caesar0301/flowjet
6
6
  Project-URL: Repository, https://github.com/caesar0301/flowjet
@@ -22,7 +22,7 @@ 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<2.0.0,>=1.2.0
25
+ Requires-Dist: soothe<2.0.0,>=1.2.1
26
26
  Provides-Extra: dev
27
27
  Requires-Dist: httpx>=0.27.0; extra == 'dev'
28
28
  Requires-Dist: mypy>=1.0.0; extra == 'dev'
@@ -34,7 +34,6 @@ Requires-Dist: ruff>=0.12.0; extra == 'dev'
34
34
  Requires-Dist: twine>=5.0.0; extra == 'dev'
35
35
  Provides-Extra: server
36
36
  Requires-Dist: agent-client-protocol[http]<1.0.0,>=0.12.1; extra == 'server'
37
- Requires-Dist: arcbox>=0.1.0; extra == 'server'
38
37
  Requires-Dist: fastapi>=0.115.0; extra == 'server'
39
38
  Requires-Dist: langchain-core>=0.3; extra == 'server'
40
39
  Requires-Dist: pydantic-settings>=2.0; extra == 'server'
@@ -38,11 +38,16 @@ microVM duplicated soothe-nano's sandbox capability at the wrong layer.
38
38
  - Per-session guest isolation: the sandbox is **per workspace**. Concurrent
39
39
  sessions sharing one workspace share one guest (staged serially; a peer
40
40
  mid-run skips the host push so live guest state is never clobbered).
41
- - Guest-side deletion propagation: a host file deleted between sessions is
42
- not removed from the guest (the pull direction is authoritative at close).
43
41
  - Incremental (delta) transfers: staging is a full-tree push/pull with a
44
42
  `(size, mtime_ns)` manifest skip for unchanged files.
45
43
 
44
+ > **Revision (2026-10-21, RFC-RD-607)**: guest-side deletion propagation is no
45
+ > longer a non-goal — `pull` now removes host files that were recorded in the
46
+ > last push manifest but disappeared from the guest tree (so agent-side
47
+ > deletes flow guest → host → sync checkpoint → content tree). Host files
48
+ > created after the push are never in the manifest and can never be deleted
49
+ > by this path.
50
+
46
51
  ## 4. Configuration
47
52
 
48
53
  Enabled by a `sandbox:` section in the flowjet nano.yml. Flowjet owns the
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "flowjet"
7
- version = "2.1.0"
7
+ version = "2.1.2"
8
8
  description = "FlowJet — agent CLI (fj) and server (ACP + OpenAI-compatible HTTP) powered by Soothe"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -27,7 +27,7 @@ classifiers = [
27
27
  ]
28
28
  # Single-dependency CLI wheel; HTTP service deps live in the `server` extra.
29
29
  dependencies = [
30
- "soothe>=1.2.0,<2.0.0",
30
+ "soothe>=1.2.1,<2.0.0",
31
31
  ]
32
32
 
33
33
  [project.optional-dependencies]
@@ -40,9 +40,6 @@ server = [
40
40
  "langchain-core>=0.3",
41
41
  # S3/MinIO workspace sync backend
42
42
  "s3fs>=2024.6",
43
- # ArcBox microVM SDK — sandboxed tool execution via soothe's
44
- # sandbox backend (https://github.com/arcboxlabs/arcbox)
45
- "arcbox>=0.1.0",
46
43
  ]
47
44
  dev = [
48
45
  "pytest>=8.0.0",
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: logo-generator
2
+ name: logo-generator-skill
3
3
  description: >
4
4
  Generate professional SVG logos and high-end showcase images. Use when the user wants to: (1) Create a logo or icon for their product/brand, (2) Generate logo design concepts based on product information, (3) Create professional logo showcase presentations with multiple background styles, (4) Export logos in various formats (SVG, PNG), or (5) Iterate on logo designs with different visual styles. Supports geometric patterns, dot matrix designs, line systems, and mixed compositions. Generates showcase images using Nano Banana (Gemini image generation) with 12 professional background styles.
5
5
  ---
@@ -276,16 +276,28 @@ class AnswerWriter:
276
276
  return self.buf
277
277
 
278
278
 
279
- def format_questions_block(questions: tuple[dict[str, Any], ...], thread_id: str) -> str:
279
+ def format_questions_block(
280
+ questions: tuple[dict[str, Any], ...],
281
+ thread_id: str,
282
+ resume_hint: str | None = None,
283
+ ) -> str:
280
284
  """Render ``ask_user`` questions as a terminal block with a resume hint.
281
285
 
282
286
  The block is written to stdout after the ephemeral progress line is
283
287
  cleared, so it stays visible. Each question lists its options with the
284
288
  recommended one (label ending in "(Recommended)") marked. The footer
285
- tells the user how to resume with an answer via ``fjf``.
289
+ tells the user how to resume with an answer via ``fjf``. Non-CLI hosts
290
+ (ACP) pass ``resume_hint=""`` to omit the CLI-specific footer — the user
291
+ answers in the host's own input.
286
292
  """
293
+ footer = (
294
+ f"Resume with: fjf -t {thread_id} <your answer>" if resume_hint is None else resume_hint
295
+ )
287
296
  if not questions:
288
- return f"\n⏸ Paused for input · resume with: fjf -t {thread_id} <your answer>\n"
297
+ block = "\n⏸ Paused for input"
298
+ if footer:
299
+ block += f" · {footer}"
300
+ return block + "\n"
289
301
  lines: list[str] = ["", "⏸ Paused for input:"]
290
302
  for qi, q in enumerate(questions, 1):
291
303
  header = str(q.get("header") or "").strip()
@@ -310,7 +322,8 @@ def format_questions_block(questions: tuple[dict[str, Any], ...], thread_id: str
310
322
  lines.append(f" • {label}{marker} — {desc}")
311
323
  else:
312
324
  lines.append(f" • {label}{marker}")
313
- lines.append(f"\n Resume with: fjf -t {thread_id} <your answer>\n")
325
+ if footer:
326
+ lines.append(f"\n {footer}\n")
314
327
  return "\n".join(lines) + "\n"
315
328
 
316
329
 
@@ -7,6 +7,7 @@ import hashlib
7
7
  import logging
8
8
  import re
9
9
  import shutil
10
+ from dataclasses import dataclass
10
11
  from pathlib import Path
11
12
  from typing import Any
12
13
 
@@ -110,6 +111,29 @@ class WorkspaceResolver:
110
111
  return path == self._home
111
112
 
112
113
 
114
+ @dataclass
115
+ class _OpenWorkspaceEntry:
116
+ """One open workspace — shared by all concurrent turns of the same run_id.
117
+
118
+ ``active_runs`` counts turns currently mid-run on this workspace; the
119
+ lifecycle (``manager.close``) belongs to the last run out. ``manifest``
120
+ caches the recovered engine manifest so the per-turn refresh can protect
121
+ agent artifacts from deletion reconcile.
122
+ """
123
+
124
+ ws: Any
125
+ active_runs: int = 0
126
+ manifest: Any | None = None
127
+ restored: bool = False
128
+
129
+
130
+ def _manifest_artifact_paths(manifest: Any | None) -> list[str]:
131
+ """Relative paths of the manifest's artifacts (reconcile protection set)."""
132
+ if manifest is None:
133
+ return []
134
+ return [entry.path for entry in (getattr(manifest, "artifacts", None) or [])]
135
+
136
+
113
137
  class SyncAwareWorkspaceResolver(WorkspaceResolver):
114
138
  """Workspace resolver backed by soothe's fsspec workspace sync.
115
139
 
@@ -150,7 +174,7 @@ class SyncAwareWorkspaceResolver(WorkspaceResolver):
150
174
  self._manager: Any | None = None
151
175
  self._sync_backend: Any | None = None
152
176
  self._restore_cas: Any | None = None
153
- self._open_workspaces: dict[str, Any] = {}
177
+ self._open_workspaces: dict[str, _OpenWorkspaceEntry] = {}
154
178
 
155
179
  @property
156
180
  def is_sync_enabled(self) -> bool:
@@ -263,7 +287,17 @@ class SyncAwareWorkspaceResolver(WorkspaceResolver):
263
287
  return path
264
288
 
265
289
  async def open_session(self, session: str, workspace: Path) -> None:
266
- """Materialize workspace from object store + start dirty tracking."""
290
+ """Refresh the workspace from the object store + start dirty tracking.
291
+
292
+ RFC-RD-607 §4.3: the sync boundary is the agent turn (one pair of
293
+ open/close hooks per prompt). Every turn unconditionally materializes
294
+ the content tree — including the reuse path where a peer session's
295
+ turn already holds the workspace open (sha-skip keeps repeat turns
296
+ cheap). Deletion reconcile runs only while this turn is the sole
297
+ active run on the workspace: deleting files under a peer's feet is
298
+ worse than a delayed delete, so peer-occupied turns skip it (logged).
299
+ Prior agent artifacts are restored once, on the opening turn.
300
+ """
267
301
  manager = self._ensure_manager()
268
302
  if manager is None:
269
303
  return
@@ -273,18 +307,39 @@ class SyncAwareWorkspaceResolver(WorkspaceResolver):
273
307
  # for perfect consistency across resolve/open/close.
274
308
  run_id = workspace.name
275
309
 
276
- # If a workspace is already open for this session (reuse), skip.
277
- if run_id in self._open_workspaces:
278
- return
310
+ entry = self._open_workspaces.get(run_id)
311
+ if entry is None:
312
+ ws = await manager.open(run_id)
313
+ entry = _OpenWorkspaceEntry(ws=ws)
314
+ self._open_workspaces[run_id] = entry
315
+ else:
316
+ ws = entry.ws
317
+ entry.active_runs += 1
318
+
319
+ if entry.manifest is None:
320
+ try:
321
+ entry.manifest = await self._recover_manifest(ws)
322
+ except Exception:
323
+ logger.warning("Manifest recovery failed for run %s", run_id, exc_info=True)
279
324
 
280
- ws = await manager.open(run_id)
281
325
  # Dual-source sync, read path: materialize the content root (e.g.
282
326
  # triarch workspace materials) into the workspace BEFORE replaying
283
327
  # checkpoint state, so the agent sees both its prior outputs and the
284
- # latest backend-uploaded files.
328
+ # latest backend-uploaded files. The dirty tracker only watches
329
+ # input/working/output + root files, so content-tree writes never
330
+ # pollute the dirty set.
285
331
  if hasattr(ws, "materialize_content"):
286
332
  try:
287
- await ws.materialize_content()
333
+ if self._supports_content_reconcile(ws):
334
+ await ws.materialize_content(
335
+ protected=_manifest_artifact_paths(entry.manifest),
336
+ reconcile=entry.active_runs == 1,
337
+ )
338
+ else:
339
+ # soothe < 2.0: no protected/reconcile kwargs — degrade to
340
+ # a plain materialize (no reconcile; deletes stay until
341
+ # the soothe 2.0.0 release train lands).
342
+ await ws.materialize_content()
288
343
  except Exception:
289
344
  logger.warning(
290
345
  "Content materialization failed for run %s; continuing with "
@@ -292,19 +347,43 @@ class SyncAwareWorkspaceResolver(WorkspaceResolver):
292
347
  run_id,
293
348
  exc_info=True,
294
349
  )
295
- await self._restore_prior_state(ws)
296
- self._open_workspaces[run_id] = ws
350
+
351
+ if entry.manifest is not None and not entry.restored:
352
+ await self._restore_prior_state(ws, entry.manifest)
353
+ entry.restored = True
297
354
 
298
355
  # Start dirty tracking + background uploader for incremental persistence.
299
356
  await ws.start_tracking()
300
357
  logger.info("Opened sync workspace for session %s at %s", session, workspace)
301
358
 
302
- async def _restore_prior_state(self, ws: Any) -> None:
303
- """Recover the prior manifest and materialize its files locally.
359
+ @staticmethod
360
+ def _supports_content_reconcile(ws: Any) -> bool:
361
+ """True when the workspace's ``materialize_content`` takes RFC-RD-607 kwargs.
362
+
363
+ soothe < 2.0 lacks ``protected``/``reconcile``; probing the signature
364
+ keeps flowjet runnable against both release trains.
365
+ """
366
+ import inspect
367
+
368
+ try:
369
+ params = inspect.signature(ws.materialize_content).parameters
370
+ except (TypeError, ValueError):
371
+ return False
372
+ return "protected" in params and "reconcile" in params
373
+
374
+ async def _recover_manifest(self, ws: Any) -> Any | None:
375
+ """Recover the prior engine manifest snapshot (single call per open).
304
376
 
305
377
  Round-trip half of workspace sync: ``close_session`` checkpoints and
306
- uploads dirty files; this restores them so a fresh server process (or
307
- an evicted session workspace) sees the agent's prior outputs.
378
+ uploads dirty files; this reads back the manifest so the per-turn
379
+ materialize can protect agent artifacts and the opening turn can
380
+ restore them.
381
+ """
382
+ payload = await ws.recover() # reads the latest checkpoint payload
383
+ return payload.manifest_snapshot if payload is not None else None
384
+
385
+ async def _restore_prior_state(self, ws: Any, manifest: Any | None) -> None:
386
+ """Materialize the recovered manifest's files locally.
308
387
 
309
388
  - Agent-written files live in ``manifest.artifacts`` (path → sha256,
310
389
  blobs under ``<root>/blobs/``) and are restored in place at their
@@ -312,8 +391,6 @@ class SyncAwareWorkspaceResolver(WorkspaceResolver):
312
391
  - Declared input resources (``manifest.resources``) are materialized
313
392
  under ``<root>/input/`` via the soothe Materializer.
314
393
  """
315
- payload = await ws.recover() # restores checkpoint state, returns snapshot payload
316
- manifest = payload.manifest_snapshot if payload is not None else None
317
394
  if manifest is None:
318
395
  return
319
396
 
@@ -363,20 +440,32 @@ class SyncAwareWorkspaceResolver(WorkspaceResolver):
363
440
  return self._restore_cas
364
441
 
365
442
  async def close_session(self, session: str, workspace: Path) -> None:
366
- """Checkpoint dirty files + upload to object store + close workspace."""
443
+ """Checkpoint dirty files + upload to object store; close when last run ends.
444
+
445
+ Concurrent turns of the same workspace share one open workspace:
446
+ a mid-run peer's close checkpoints the shared state but keeps the
447
+ workspace open — the lifecycle (``manager.close``) belongs to the
448
+ last run out (RFC-RD-607 §4.3).
449
+ """
367
450
  if self._manager is None:
368
451
  return
369
452
 
370
453
  run_id = workspace.name
371
454
 
372
- ws = self._open_workspaces.pop(run_id, None)
373
- if ws is None:
455
+ entry = self._open_workspaces.get(run_id)
456
+ if entry is None:
374
457
  return
375
458
 
459
+ if entry.active_runs > 0:
460
+ entry.active_runs -= 1
376
461
  try:
377
- await ws.checkpoint()
462
+ await entry.ws.checkpoint()
378
463
  except Exception:
379
464
  logger.warning("Checkpoint failed for session %s", session, exc_info=True)
465
+ if entry.active_runs > 0:
466
+ return
467
+
468
+ self._open_workspaces.pop(run_id, None)
380
469
  try:
381
470
  await self._manager.close(run_id)
382
471
  except Exception:
@@ -26,12 +26,10 @@ def _import_arcbox() -> Any:
26
26
  import arcbox # type: ignore[import-not-found]
27
27
  except ImportError as exc: # pragma: no cover - environment-dependent
28
28
  raise RuntimeError(
29
- "sandbox.enabled=true in nano.yml requires the external arcbox SDK "
30
- "(pip install arcbox, already part of the flowjet `server` extra) "
31
- "and the arcbox daemon running on the host (arcbox serve). The "
32
- "daemon manages microVMs via Hypervisor.framework (macOS) or KVM "
33
- "(Linux). Remove the sandbox: section or set sandbox.enabled=false "
34
- "to run tools host-side."
29
+ "sandbox.enabled=true in nano.yml requires the arcbox SDK, which ships "
30
+ "transitively via soothe-nano (a dependency of soothe). Reinstall flowjet "
31
+ "or run `arcbox serve` on the host. Remove the sandbox: section or set "
32
+ "sandbox.enabled=false to run tools host-side."
35
33
  ) from exc
36
34
  return arcbox
37
35
 
@@ -118,12 +116,18 @@ class WorkspaceStager:
118
116
  ) -> dict[str, tuple[int, int]]:
119
117
  """Download the guest workspace tree back to the host (skip identical).
120
118
 
121
- Returns the manifest for the next push.
119
+ Deletion propagation (RFC-RD-607 §4.4): files the agent deleted inside
120
+ the guest are absent from its tree and are removed from the host too.
121
+ Only paths recorded in the push manifest are eligible — host files
122
+ created after the push are never in it, so they can never be deleted
123
+ by this path. Returns the manifest for the next push.
122
124
  """
123
125
  root = Path(workspace)
124
126
  guest_root = PurePosixPath(self._settings.workspace_root)
125
127
  pulled = 0
128
+ deleted = 0
126
129
  next_manifest: dict[str, tuple[int, int]] = {}
130
+ guest_files: set[str] = set()
127
131
 
128
132
  async def walk(guest_dir: PurePosixPath, rel_dir: PurePosixPath) -> None:
129
133
  nonlocal pulled
@@ -144,6 +148,7 @@ class WorkspaceStager:
144
148
  continue
145
149
  if entry.kind != "file":
146
150
  continue
151
+ guest_files.add(rel_entry.as_posix())
147
152
  data = await sandbox.files.read_bytes(guest_path)
148
153
  dest = root / Path(*rel_entry.parts)
149
154
  if dest.is_file():
@@ -159,8 +164,34 @@ class WorkspaceStager:
159
164
  pulled += 1
160
165
 
161
166
  await walk(guest_root, PurePosixPath("."))
162
- if pulled:
163
- logger.info("Sandbox pull for %s: %d files", workspace, pulled)
167
+ for rel in manifest:
168
+ if rel in guest_files:
169
+ continue
170
+ dest = root / Path(*PurePosixPath(rel).parts)
171
+ if not dest.is_file() or dest.is_symlink():
172
+ continue
173
+ try:
174
+ await asyncio.to_thread(dest.unlink)
175
+ except OSError:
176
+ logger.warning("Sandbox pull: host delete failed for %s in %s", rel, workspace)
177
+ continue
178
+ deleted += 1
179
+ parent = dest.parent
180
+ while parent != root and parent.is_relative_to(root):
181
+ if any(parent.iterdir()):
182
+ break
183
+ try:
184
+ parent.rmdir()
185
+ except OSError:
186
+ break
187
+ parent = parent.parent
188
+ if pulled or deleted:
189
+ logger.info(
190
+ "Sandbox pull for %s: %d files pulled, %d guest deletions propagated",
191
+ workspace,
192
+ pulled,
193
+ deleted,
194
+ )
164
195
  return next_manifest
165
196
 
166
197
 
@@ -462,6 +462,24 @@ class FlowJetAcpAgent:
462
462
  session_id,
463
463
  update_agent_thought_text(event.message or "Waiting for input"),
464
464
  )
465
+ # Surface the ask_user questions as message chunks, not
466
+ # just a thought: hosts accumulate message chunks as the
467
+ # turn's reply, so a clarification turn would otherwise
468
+ # end with no content at all (thoughts are intermediate
469
+ # narration). The CLI renders the same block.
470
+ if event.questions:
471
+ from flowjet.cli.stream import format_questions_block
472
+
473
+ block = format_questions_block(
474
+ event.questions,
475
+ session_id,
476
+ resume_hint="",
477
+ )
478
+ await client.session_update(
479
+ session_id,
480
+ update_agent_message_text(block),
481
+ )
482
+ output_text += block
465
483
  elif isinstance(event, RunCompleted):
466
484
  output_text = event.output_text or output_text
467
485
  usage_info = event.usage
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