flowjet 2.1.4__tar.gz → 2.1.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. {flowjet-2.1.4 → flowjet-2.1.6}/CHANGELOG.md +52 -0
  2. {flowjet-2.1.4 → flowjet-2.1.6}/PKG-INFO +1 -1
  3. {flowjet-2.1.4 → flowjet-2.1.6}/docs/specs/RFC-005-arcbox-tool-sandbox.md +68 -3
  4. {flowjet-2.1.4 → flowjet-2.1.6}/docs/specs/rfc-history.md +1 -1
  5. {flowjet-2.1.4 → flowjet-2.1.6}/pyproject.toml +1 -1
  6. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/stream.py +33 -0
  7. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/bridges/base.py +53 -13
  8. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/sandbox/arbox.py +359 -21
  9. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/sandbox/settings.py +43 -0
  10. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/acp/agent.py +2 -6
  11. {flowjet-2.1.4 → flowjet-2.1.6}/.gitignore +0 -0
  12. {flowjet-2.1.4 → flowjet-2.1.6}/LICENSE +0 -0
  13. {flowjet-2.1.4 → flowjet-2.1.6}/README.md +0 -0
  14. {flowjet-2.1.4 → flowjet-2.1.6}/config/nano-local.yml +0 -0
  15. {flowjet-2.1.4 → flowjet-2.1.6}/config/nano-sandbox-soothe.yml +0 -0
  16. {flowjet-2.1.4 → flowjet-2.1.6}/config/nano.yml +0 -0
  17. {flowjet-2.1.4 → flowjet-2.1.6}/deploy/README.md +0 -0
  18. {flowjet-2.1.4 → flowjet-2.1.6}/docs/completion-design.md +0 -0
  19. {flowjet-2.1.4 → flowjet-2.1.6}/docs/impl/IG-001-openai-compatible-server.md +0 -0
  20. {flowjet-2.1.4 → flowjet-2.1.6}/docs/impl/IG-002-isolated-thread-pool-runtime.md +0 -0
  21. {flowjet-2.1.4 → flowjet-2.1.6}/docs/impl/IG-003-production-isolation-hardening.md +0 -0
  22. {flowjet-2.1.4 → flowjet-2.1.6}/docs/specs/RFC-001-openai-compatible-api.md +0 -0
  23. {flowjet-2.1.4 → flowjet-2.1.6}/docs/specs/RFC-002-isolated-thread-pool-runtime.md +0 -0
  24. {flowjet-2.1.4 → flowjet-2.1.6}/docs/specs/RFC-003-production-isolation-hardening.md +0 -0
  25. {flowjet-2.1.4 → flowjet-2.1.6}/docs/specs/RFC-004-unified-package.md +0 -0
  26. {flowjet-2.1.4 → flowjet-2.1.6}/docs/specs/rfc-index.md +0 -0
  27. {flowjet-2.1.4 → flowjet-2.1.6}/docs/specs/rfc-namings.md +0 -0
  28. {flowjet-2.1.4 → flowjet-2.1.6}/examples/README.md +0 -0
  29. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/__init__.py +0 -0
  30. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/__main__.py +0 -0
  31. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/README.md +0 -0
  32. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/gws-drive/SKILL.md +0 -0
  33. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/gws-drive-upload/SKILL.md +0 -0
  34. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/logo-generator-skill/.env.example +0 -0
  35. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/logo-generator-skill/README.md +0 -0
  36. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/logo-generator-skill/SKILL.md +0 -0
  37. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/logo-generator-skill/assets/background_library.html +0 -0
  38. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/logo-generator-skill/assets/showcase_template.html +0 -0
  39. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/logo-generator-skill/references/background_styles.md +0 -0
  40. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/logo-generator-skill/references/design_patterns.md +0 -0
  41. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/logo-generator-skill/references/webgl_backgrounds.md +0 -0
  42. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/logo-generator-skill/requirements.txt +0 -0
  43. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/logo-generator-skill/scripts/generate_showcase.py +0 -0
  44. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/logo-generator-skill/scripts/svg_to_png.py +0 -0
  45. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/SKILL.md +0 -0
  46. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/brainstorming/spec-document-reviewer-prompt.md +0 -0
  47. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/implementation/coding-plan-template.md +0 -0
  48. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/implementation/impl-guide-template.md +0 -0
  49. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/review/pr-review-template.md +0 -0
  50. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/review/review-checklist.md +0 -0
  51. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/specs/rfc-template.md +0 -0
  52. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-architecture-design.md +0 -0
  53. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-conceptual-design.md +0 -0
  54. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-impl-interface-design.md +0 -0
  55. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-drafts-readme.md +0 -0
  56. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-impl-readme.md +0 -0
  57. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-platonic.yml +0 -0
  58. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-history.md +0 -0
  59. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-index.md +0 -0
  60. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-namings.md +0 -0
  61. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-standard.md +0 -0
  62. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/BRAINSTORM/brainstorm.md +0 -0
  63. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-code.md +0 -0
  64. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-create-guide.md +0 -0
  65. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-full.md +0 -0
  66. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-update-guide.md +0 -0
  67. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-validate-guide.md +0 -0
  68. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-plan-modular-specs.md +0 -0
  69. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-architecture.md +0 -0
  70. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-conceptual.md +0 -0
  71. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-impl-interface.md +0 -0
  72. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-scaffold.md +0 -0
  73. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-scan.md +0 -0
  74. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/REFERENCE.md +0 -0
  75. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/REVIEW/review-spec-compliance.md +0 -0
  76. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-check-compliance.md +0 -0
  77. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-check-taxonomy.md +0 -0
  78. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-history.md +0 -0
  79. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-index.md +0 -0
  80. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-namings.md +0 -0
  81. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-refine.md +0 -0
  82. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-validate-consistency.md +0 -0
  83. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-overview.md +0 -0
  84. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-1.md +0 -0
  85. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-2.md +0 -0
  86. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-3.md +0 -0
  87. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/SKILL.md +0 -0
  88. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/evidence-deep.json +0 -0
  89. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/evidence-first.json +0 -0
  90. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/idea-first.json +0 -0
  91. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/loop.json +0 -0
  92. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/paper-pipeline.json +0 -0
  93. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/rapid.json +0 -0
  94. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/rigor-first.json +0 -0
  95. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/stance-first.json +0 -0
  96. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/systematic-review.json +0 -0
  97. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/verify-loop.json +0 -0
  98. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/references/backends.md +0 -0
  99. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/references/handoff-ars-omr.md +0 -0
  100. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/references/handoff-omr-ars.md +0 -0
  101. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/references/routing.md +0 -0
  102. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/scripts/csljson_to_omr.py +0 -0
  103. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/scripts/papers_index_to_csljson.py +0 -0
  104. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/__init__.py +0 -0
  105. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/__main__.py +0 -0
  106. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/agent.py +0 -0
  107. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/cli.py +0 -0
  108. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/completion/__init__.py +0 -0
  109. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/completion/context.py +0 -0
  110. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/completion/engine.py +0 -0
  111. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/doctor_cmd.py +0 -0
  112. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/doctor_fix_cmd.py +0 -0
  113. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/progress.py +0 -0
  114. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/serve_cmd.py +0 -0
  115. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/setup_cmd.py +0 -0
  116. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/shell/__init__.py +0 -0
  117. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/shell/_fj.zsh +0 -0
  118. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/shell/fj.bash +0 -0
  119. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/threads.py +0 -0
  120. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/tool_stream.py +0 -0
  121. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/__init__.py +0 -0
  122. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/_warnings.py +0 -0
  123. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/agent/__init__.py +0 -0
  124. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/agent/config.py +0 -0
  125. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/agent/factory.py +0 -0
  126. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/agent/skills.py +0 -0
  127. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/agent/wiring.py +0 -0
  128. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/agent/workspace.py +0 -0
  129. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/backend_config.py +0 -0
  130. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/bridges/nano/__init__.py +0 -0
  131. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/bridges/nano/adapter.py +0 -0
  132. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/bridges/nano/backend.py +0 -0
  133. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/bridges/nano/interrupts.py +0 -0
  134. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/bridges/nano/mapping.py +0 -0
  135. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/bridges/soothe/__init__.py +0 -0
  136. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/bridges/soothe/adapter.py +0 -0
  137. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/bridges/soothe/backend.py +0 -0
  138. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/events.py +0 -0
  139. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/identity.py +0 -0
  140. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/modes.py +0 -0
  141. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/protocol.py +0 -0
  142. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/__init__.py +0 -0
  143. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/direct.py +0 -0
  144. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/fake.py +0 -0
  145. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/isolation/__init__.py +0 -0
  146. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/isolation/adapter.py +0 -0
  147. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/isolation/admission.py +0 -0
  148. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/isolation/backend.py +0 -0
  149. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/isolation/errors.py +0 -0
  150. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/isolation/mode_gate.py +0 -0
  151. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/isolation/pool.py +0 -0
  152. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/isolation/request.py +0 -0
  153. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/isolation/workspace.py +0 -0
  154. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/sandbox/__init__.py +0 -0
  155. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/sandbox/resolver.py +0 -0
  156. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/streaming/__init__.py +0 -0
  157. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/streaming/presentation.py +0 -0
  158. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/streaming/tool_results.py +0 -0
  159. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/streaming/tool_stream.py +0 -0
  160. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/home.py +0 -0
  161. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/__init__.py +0 -0
  162. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/__main__.py +0 -0
  163. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/acp/__init__.py +0 -0
  164. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/acp/routes.py +0 -0
  165. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/config.py +0 -0
  166. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/http/__init__.py +0 -0
  167. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/http/app.py +0 -0
  168. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/http/auth.py +0 -0
  169. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/openai_compat/__init__.py +0 -0
  170. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/openai_compat/errors.py +0 -0
  171. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/openai_compat/projection.py +0 -0
  172. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/openai_compat/routes.py +0 -0
  173. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/openai_compat/schemas.py +0 -0
  174. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/openai_compat/service.py +0 -0
  175. {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/openai_compat/store.py +0 -0
@@ -10,6 +10,58 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [2.1.6] — 2026-10-24
14
+
15
+ ### Fixed
16
+
17
+ - **ArcBox tool sandboxes stay usable across recycles, crashes and restarts.**
18
+ The manager trusted its in-memory lease map, and the agent graph pinned its
19
+ sandbox id at build time, so a guest that died behind a still-`ready` daemon
20
+ record (host sleep, idle recycle, daemon restart) left the worker talking to a
21
+ corpse: every file/shell tool failed and the agent reported an empty
22
+ workspace even though the host tree was fully staged. `open_workspace` now
23
+ probes readiness once per turn — a control-plane `info()` tier that catches a
24
+ record outliving its VM plus terminal states, then a guest round-trip that
25
+ catches a wedged guest — and kills, drops and replaces a lease that fails.
26
+ Every new guest, whether spawned or adopted, is seeded from the host
27
+ workspace, and in-flight run counts carry over so peer open/close pairs stay
28
+ balanced. The agent cache key is now workspace **+ sandbox id**, so a respawn
29
+ rebuilds the graph against the live guest.
30
+ - **Crash and restart recovery.** VMs are created with a labelled orphan
31
+ deadline (`idle_timeout` + `on_idle=kill`) that the reaper re-arms every 60 s,
32
+ so a process that dies leaves nothing behind — the daemon reaps it, with no
33
+ cross-process coordination and no startup kill-list. On first use the manager
34
+ scans owner-scoped sandboxes and adopts a surviving VM, pulling guest state to
35
+ the host before pushing (recovering a crashed turn's artifacts) instead of
36
+ spawning a duplicate; a failed create reconnects the client and retries once.
37
+ - **Every control-plane RPC is bounded** (`kill`/`set_lifecycle` 15 s, `connect`
38
+ by `connect_timeout`). The SDK's per-RPC deadline is opt-in and was
39
+ unconfigured: one wedged call stalled the reaper loop, and a stalled heartbeat
40
+ reads to the daemon exactly like a dead owner — which gets healthy, in-use VMs
41
+ reaped at their deadlines.
42
+
43
+ ### Changed
44
+
45
+ - `sandbox.orphan_timeout_seconds` defaults to `max(900, 3 × idle_timeout_seconds)`
46
+ instead of a fixed 900, and an explicit value is validated only when the
47
+ sandbox is enabled — existing configs keep parsing unchanged. New keys:
48
+ `recover_orphans` (adopt surviving VMs after a restart) and `owner` (label
49
+ namespace, env `FLOWJET_SANDBOX_OWNER`) so planes sharing one ArcBox daemon
50
+ never adopt or reap each other's sandboxes. See RFC-005 §5.1/§5.2.
51
+
52
+ ## [2.1.5] — 2026-10-24
53
+
54
+ ### Fixed
55
+
56
+ - **`ask_user` pauses render as Markdown on ACP/UI hosts.** A clarification
57
+ turn's reply reused the CLI terminal block (space-indented `⏸ Paused for
58
+ input:` followed by `• option` lines). Hosts that render replies as Markdown
59
+ collapse the indentation, so the pause and its options displayed as a single
60
+ run-on paragraph. The ACP path now emits `format_questions_markdown()`: a
61
+ bold pause heading, a bold question (Q1:/Q2: numbering for multiple
62
+ questions), and options as `- **label** — description` bullets, keeping the
63
+ `(Recommended)` → ★ convention. The CLI keeps the plain-text block.
64
+
13
65
  ## [2.1.4] — 2026-10-23
14
66
 
15
67
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: flowjet
3
- Version: 2.1.4
3
+ Version: 2.1.6
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
@@ -68,6 +68,9 @@ sandbox:
68
68
  connect_timeout: 60
69
69
  idle_timeout_seconds: 300 # idle sandbox recycle
70
70
  max_sandboxes: 8 # 0 (unlimited), None (70% usable CPU / vcpus, min 8), or int
71
+ orphan_timeout_seconds: null # daemon-side crash deadline; null → max(900, 3× idle)
72
+ recover_orphans: true # adopt a surviving VM after a restart
73
+ owner: flowjet # sandbox label namespace; env FLOWJET_SANDBOX_OWNER
71
74
  # FLOWJET_SANDBOX_MAX (int; 0=unlimited) and FLOWJET_SANDBOX_MAX_PERCENT (1-100, default 70) env overrides
72
75
  # api_url / api_key: optional; env fallback ARCBOX_API_URL / ARCBOX_API_KEY
73
76
  # soothe-nano placement policy (pass-through):
@@ -92,7 +95,7 @@ flowchart LR
92
95
  E[WorkspaceResolver<br/>/ SyncAware] -->|open: push host→guest<br/>close: pull guest→host| D
93
96
  ```
94
97
 
95
- - `flowjet.core.backends.tool_sandbox.ArcBoxToolSandboxManager` — one guest VM
98
+ - `flowjet.core.sandbox.arbox.ArcBoxToolSandboxManager` — one guest VM
96
99
  per workspace: lazy spawn (`AsyncArcBox.create(..., wait_until_ready=True)`),
97
100
  idle reaper, LRU eviction at `max_sandboxes` (explicit int, `None`, or `0`
98
101
  for unlimited — `None` sizes from host CPU: 70% usable ÷ per-sandbox vCPUs,
@@ -102,11 +105,73 @@ flowchart LR
102
105
  runs inside the session lifecycle: open = inner materialize → host push;
103
106
  close = guest pull → inner checkpoint (the sync checkpoint must see guest
104
107
  outputs).
105
- - Adapter binding — the sandbox backend is bound at agent build time; the
106
- adapter rebuilds its agent when the pool worker switches workspaces. In
108
+ - Adapter binding — the sandbox backend is pinned at agent build time, so the
109
+ adapter caches the built agent on **workspace + sandbox id**, not the path
110
+ alone: a respawn, idle recycle or eviction yields a new id and the agent is
111
+ rebuilt (a stale binding fails every guest tool call with `VM not found`). In
107
112
  sandbox mode the request's `configurable["workspace"]` is the **guest root**
108
113
  (`/workspace`), not the host path — matching soothe-nano sandbox semantics
109
114
  (system prompt, workspace policy, and recon all target the guest root).
115
+
116
+ ### 5.1 Readiness invariant
117
+
118
+ `open_workspace` runs once per turn on the main loop, *before* the agent
119
+ streams, and is the only place a guest is created or validated; the sync
120
+ `backend_for` (worker threads) never spawns. It guarantees a *usable* guest:
121
+
122
+ 1. **Probe** — two tiers, cheap first. Tier 1 is `sandbox.info()`: one unary
123
+ RPC, "always fresh, never cached", no guest execution, no lifecycle side
124
+ effects — it catches the observed failure class (the daemon's record
125
+ *outliving the VM*: `list()` still says `ready` while every call fails
126
+ with `VM not found`) and terminal states (`stopping`/`stopped`/`failed`).
127
+ Tier 2 is a guest round-trip (`true`), catching a wedged guest (VM
128
+ present, nothing executing); commands run as independent process groups,
129
+ so it never queues behind a peer's long-running command. A record is
130
+ never trusted on its own.
131
+ 2. **Replace** — a lease that fails the probe is killed and dropped; the
132
+ replacement is seeded from the host tree. In-flight run counts carry over so
133
+ peer open/close pairs stay balanced, and the reseed is forced even with a
134
+ peer mid-run (a new VM is empty — skipping the push is how materials go
135
+ missing).
136
+ 3. **Reseed** — every path that yields a new guest (spawn, respawn, adoption)
137
+ pushes the host workspace before the turn runs.
138
+
139
+ Every control-plane RPC in the manager is bounded (`kill` / `set_lifecycle`
140
+ by a 15 s deadline, `connect` by `connect_timeout`): the SDK's per-RPC
141
+ deadline is opt-in and unconfigured here, and one wedged call would otherwise
142
+ stall the reaper loop — a stalled heartbeat reads to the daemon exactly like
143
+ a dead owner, which gets *healthy* VMs reaped at their deadlines.
144
+
145
+ ### 5.2 Crash and restart
146
+
147
+ The manager holds its leases in memory, so a `flowjet-server` crash loses the
148
+ map while the daemon keeps the VMs. Three mechanisms keep that safe:
149
+
150
+ - **Daemon-side orphan deadline** — each VM is created with
151
+ `idle_timeout=<orphan deadline>` + `on_idle="kill"`; the manager
152
+ *re-arms* that timer every `_HEARTBEAT_INTERVAL_SECONDS` (60 s, one
153
+ bounded `set_lifecycle` per lease). Expiry therefore means "the owning
154
+ process is gone", and the daemon reaps the VM with no cross-process
155
+ coordination and no startup kill-list. A lease that cannot be re-armed is
156
+ unreachable and is dropped immediately. The deadline defaults to
157
+ `max(900, 3 × idle_timeout_seconds)` when unset, so an existing config with
158
+ a long idle reaper parses unchanged and is never preempted by a daemon
159
+ kill; an explicit value must exceed `idle_timeout_seconds` (validated only
160
+ when the sandbox is enabled).
161
+ - **Startup scan + adoption** — on first use the manager lists owner-scoped
162
+ sandboxes carrying its labels (`flowjet.sandbox{,-owner,-instance,-workspace}`).
163
+ Entries from another instance are recovery *candidates*, not victims: the
164
+ first turn for that workspace connects, probes, then **pulls guest → host**
165
+ (recovering files the interrupted turn wrote but never pulled) before pushing
166
+ the host tree back. No candidate (or a dead one) falls through to a fresh,
167
+ seeded VM. `recover_orphans: false` disables adoption.
168
+ - **Owner scoping** — `owner` namespaces the labels so several flowjet planes
169
+ sharing one daemon never adopt or reap each other's sandboxes. Adoption
170
+ additionally assumes one process per workspace at a time (the shipped
171
+ agentspace plane runs a single replica).
172
+
173
+ A failing `create` reconnects the client and retries once, covering a daemon
174
+ that is itself restarting.
110
175
  - Credential boundary — LLM keys and the nano config never enter the guest;
111
176
  only the staged workspace bytes do.
112
177
 
@@ -21,7 +21,7 @@ This document tracks all RFC lifecycle events in chronological order (newest fir
21
21
 
22
22
  | Date | Event | RFC / Document | Summary |
23
23
  |------|-------|----------------|---------|
24
- | 2026-10-18 | Created | [RFC-005](RFC-005-arcbox-tool-sandbox.md) | ArcBox tool sandbox via soothe-nano 2.0; whole-worker arcbox isolation removed |
24
+ | 2026-10-24 | Updated | [RFC-005](RFC-005-arcbox-tool-sandbox.md) | §5.1 two-tier readiness probe + sandbox-id agent cache key; §5.2 crash/restart recovery (daemon orphan deadline, heartbeat, orphan adoption); bounded control-plane RPCs; orphan deadline derives when unset || 2026-10-18 | Created | [RFC-005](RFC-005-arcbox-tool-sandbox.md) | ArcBox tool sandbox via soothe-nano 2.0; whole-worker arcbox isolation removed |
25
25
  | 2026-07-31 | Reference Updated | [TEST-COVERAGE-MATRIX](../impl/TEST-COVERAGE-MATRIX.md) | Feature → test attachment for RFC-001–003 |
26
26
  | 2026-07-31 | Created | [IG-003](../impl/IG-003-production-isolation-hardening.md) | Production isolation hardening guide for RFC-003 |
27
27
  | 2026-07-31 | Created | [RFC-003](RFC-003-production-isolation-hardening.md) | Production-ready ThreadPool lifecycle, bridge, and policy |
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "flowjet"
7
- version = "2.1.4"
7
+ version = "2.1.6"
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" }
@@ -420,6 +420,39 @@ def format_questions_block(
420
420
  return "\n".join(lines) + "\n"
421
421
 
422
422
 
423
+ def format_questions_markdown(questions: tuple[dict[str, Any], ...]) -> str:
424
+ """Render ``ask_user`` questions as Markdown for hosts that render replies.
425
+
426
+ Same content as :func:`format_questions_block` (the CLI renders that block
427
+ to a terminal), but in Markdown so ACP/UI hosts show a bold pause heading
428
+ with bulleted, bolded options. The plain-text block cannot be reused here:
429
+ its space indentation collapses under Markdown rendering and the whole
430
+ block displays as one run-on paragraph.
431
+ """
432
+ if not questions:
433
+ return "**⏸ Paused for input**\n"
434
+ lines: list[str] = ["**⏸ Paused for input**"]
435
+ for qi, q in enumerate(questions, 1):
436
+ header = str(q.get("header") or "").strip()
437
+ question = str(q.get("question") or "").strip()
438
+ label = f"[{header}] {question}" if header else question
439
+ prefix = f"Q{qi}: " if len(questions) > 1 else ""
440
+ lines.append(f"\n**{prefix}{label}**")
441
+ options = q.get("options")
442
+ if isinstance(options, list):
443
+ for opt in options:
444
+ if not isinstance(opt, dict):
445
+ continue
446
+ opt_label = str(opt.get("label") or "").strip()
447
+ desc = str(opt.get("description") or "").strip()
448
+ marker = " ★" if "(Recommended)" in opt_label or "Recommended" in opt_label else ""
449
+ if desc:
450
+ lines.append(f"- **{opt_label}{marker}** — {desc}")
451
+ else:
452
+ lines.append(f"- **{opt_label}{marker}**")
453
+ return "\n".join(lines) + "\n"
454
+
455
+
423
456
  async def stream_query(
424
457
  agent: SootheNanoAgent,
425
458
  query: str,
@@ -66,10 +66,12 @@ class AgentAdapterBase:
66
66
 
67
67
  Manages one agent instance per pool worker: lazily built on first use,
68
68
  bound to a workspace's sandbox when a ``sandbox_manager`` is present
69
- (rebuilt when the worker switches workspaces), and recycled after a failed
70
- or cancelled run — cancellation can interrupt middleware cleanup and leave
71
- mutable graph state (for example edit-coalescing buffers) incomplete, so
72
- that graph is never reused for another request.
69
+ (rebuilt when the worker switches workspaces *or* that workspace's sandbox
70
+ is replaced — the graph's backend is pinned to a sandbox id, not a path),
71
+ and recycled after a failed or cancelled run — cancellation can interrupt
72
+ middleware cleanup and leave mutable graph state (for example
73
+ edit-coalescing buffers) incomplete, so that graph is never reused for
74
+ another request.
73
75
 
74
76
  Thread state, by contrast, is *not* worker-local: when
75
77
  ``checkpointer_policy`` is enabled the adapter opens one checkpointer on
@@ -100,6 +102,8 @@ class AgentAdapterBase:
100
102
  self._checkpointer_cm: Any | None = None
101
103
  self._checkpointer_failed = False
102
104
  self._agent_workspace: str | None = None
105
+ #: Sandbox id the cached agent's backend is bound to (``None`` off-sandbox).
106
+ self._agent_sandbox_id: str | None = None
103
107
  self._active = False
104
108
  self._tainted = False
105
109
  self._generation = 1 if agent is not None else 0
@@ -148,20 +152,53 @@ class AgentAdapterBase:
148
152
 
149
153
  async def _ensure_agent(self, *, workspace: str | None = None) -> Any:
150
154
  """Async ensure: open the checkpointer before a config-driven build."""
151
- needs_build = self._agent is None or (
152
- self._sandbox_manager is not None and self._agent_workspace != workspace
153
- )
154
- if needs_build and self._agent_factory is None:
155
+ if self._agent_needs_rebuild(workspace) and self._agent_factory is None:
155
156
  await self._ensure_checkpointer()
156
157
  return self._ensure(workspace=workspace)
157
158
 
159
+ def _live_sandbox_id(self, workspace: str | None) -> str | None:
160
+ """Id of the guest the manager currently binds to ``workspace``."""
161
+ if self._sandbox_manager is None or workspace is None:
162
+ return None
163
+ return self._sandbox_manager.sandbox_id_for(workspace)
164
+
165
+ def _agent_needs_rebuild(self, workspace: str | None) -> bool:
166
+ """True when this worker must build a new agent for ``workspace``.
167
+
168
+ Without a sandbox manager the built agent is workspace-agnostic, so an
169
+ existing instance is reused. With one, the graph holds an
170
+ ``ArcBoxBackend`` bound at build time: either a workspace switch or a
171
+ replacement of *that workspace's* sandbox (idle recycle, dead VM,
172
+ eviction) leaves it pointing at a guest that no longer exists, where
173
+ every file and shell tool fails with ``VM not found``. The path alone
174
+ cannot detect the second case — it stays the same across generations —
175
+ so the live sandbox id is part of the cache key.
176
+ """
177
+ if self._agent is None:
178
+ return True
179
+ if self._sandbox_manager is None:
180
+ return False
181
+ return (
182
+ self._agent_workspace != workspace
183
+ or self._agent_sandbox_id != self._live_sandbox_id(workspace)
184
+ )
185
+
158
186
  def _ensure(self, *, workspace: str | None = None) -> Any:
187
+ if not self._agent_needs_rebuild(workspace):
188
+ assert self._agent is not None
189
+ return self._agent
159
190
  if self._agent is not None:
160
- if self._sandbox_manager is None or self._agent_workspace == workspace:
161
- return self._agent
162
- # The sandbox backend is bound at build time — a workspace
163
- # switch needs a fresh agent bound to the new sandbox.
164
- self._agent = None
191
+ # Bound to a workspace/sandbox generation that is no longer current.
192
+ logger.info(
193
+ "%s: rebuilding agent for %s (sandbox %s -> %s)",
194
+ self.label,
195
+ workspace,
196
+ self._agent_sandbox_id,
197
+ self._live_sandbox_id(workspace),
198
+ )
199
+ self._agent = None
200
+ self._agent_sandbox_id = None
201
+ live_sandbox_id = self._live_sandbox_id(workspace)
165
202
  if self._agent_factory is not None:
166
203
  self._agent = self._agent_factory()
167
204
  else:
@@ -170,6 +207,7 @@ class AgentAdapterBase:
170
207
  sandbox_backend = self._sandbox_manager.backend_for(workspace)
171
208
  self._agent = self._build(sandbox_backend=sandbox_backend)
172
209
  self._agent_workspace = workspace
210
+ self._agent_sandbox_id = live_sandbox_id
173
211
  self._generation += 1
174
212
  return self._agent
175
213
 
@@ -216,11 +254,13 @@ class AgentAdapterBase:
216
254
  if self._tainted:
217
255
  self._agent = None
218
256
  self._agent_workspace = None
257
+ self._agent_sandbox_id = None
219
258
  self._tainted = False
220
259
 
221
260
  async def cleanup(self) -> None:
222
261
  self._agent = None
223
262
  self._agent_workspace = None
263
+ self._agent_sandbox_id = None
224
264
  self._tainted = False
225
265
  self._active = False
226
266
  cm, self._checkpointer_cm = self._checkpointer_cm, None