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.
- {flowjet-2.0.8 → flowjet-2.0.10}/.gitignore +3 -1
- {flowjet-2.0.8 → flowjet-2.0.10}/CHANGELOG.md +31 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/PKG-INFO +5 -3
- {flowjet-2.0.8 → flowjet-2.0.10}/deploy/README.md +1 -1
- {flowjet-2.0.8 → flowjet-2.0.10}/deploy/nano.yml +1 -1
- {flowjet-2.0.8 → flowjet-2.0.10}/pyproject.toml +13 -20
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/cli.py +5 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/progress.py +2 -2
- flowjet-2.0.10/src/flowjet/core/_warnings.py +53 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backend.py +10 -7
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backends/isolation/__init__.py +14 -2
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backends/isolation/adapter.py +10 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backends/isolation/backend.py +34 -7
- flowjet-2.0.10/src/flowjet/core/backends/isolation/boxlite_protocol.py +161 -0
- flowjet-2.0.10/src/flowjet/core/backends/isolation/boxlite_vm_pool.py +668 -0
- flowjet-2.0.10/src/flowjet/core/backends/isolation/boxlite_worker.py +256 -0
- flowjet-2.0.10/src/flowjet/core/backends/isolation/isolation_pool.py +48 -0
- flowjet-2.0.10/src/flowjet/core/backends/isolation/workspace.py +411 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/bootstrap.py +1 -1
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/bridges/nano/backend.py +10 -1
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/bridges/nano/mapping.py +36 -1
- flowjet-2.0.10/src/flowjet/core/bridges/soothe/__init__.py +6 -0
- flowjet-2.0.10/src/flowjet/core/bridges/soothe/adapter.py +141 -0
- flowjet-2.0.10/src/flowjet/core/bridges/soothe/backend.py +47 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/tool_stream.py +1 -1
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/__main__.py +6 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/acp/agent.py +213 -2
- flowjet-2.0.10/src/flowjet/server/config.py +174 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/http/app.py +97 -1
- flowjet-2.0.8/docs/specs/templates/architecture-design.md +0 -139
- flowjet-2.0.8/docs/specs/templates/conceptual-design.md +0 -102
- flowjet-2.0.8/docs/specs/templates/impl-interface-design.md +0 -137
- flowjet-2.0.8/docs/specs/templates/rfc-standard.md +0 -577
- flowjet-2.0.8/src/flowjet/builtin_skills/platonic-coding/assets/specs/rfc-template.md +0 -94
- flowjet-2.0.8/src/flowjet/core/backends/isolation/workspace.py +0 -47
- flowjet-2.0.8/src/flowjet/server/config.py +0 -70
- {flowjet-2.0.8 → flowjet-2.0.10}/LICENSE +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/README.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/config/nano.yml +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/docs/completion-design.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/docs/drafts/README.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/docs/drafts/api-spec.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/docs/impl/IG-001-openai-compatible-server.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/docs/impl/IG-002-isolated-thread-pool-runtime.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/docs/impl/IG-003-production-isolation-hardening.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/docs/impl/README.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/docs/impl/TEST-COVERAGE-MATRIX.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/docs/naming.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/docs/server.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/docs/specs/RFC-001-openai-compatible-api.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/docs/specs/RFC-002-isolated-thread-pool-runtime.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/docs/specs/RFC-003-production-isolation-hardening.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/docs/specs/RFC-004-unified-package.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/docs/specs/rfc-history.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/docs/specs/rfc-index.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/docs/specs/rfc-namings.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/docs/upgrade.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/examples/README.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/nano.yml +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/__init__.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/__main__.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/README.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/gws-drive/SKILL.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/gws-drive-upload/SKILL.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/logo-generator-skill/.env.example +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/logo-generator-skill/README.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/logo-generator-skill/SKILL.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/logo-generator-skill/assets/background_library.html +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/logo-generator-skill/assets/showcase_template.html +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/logo-generator-skill/references/background_styles.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/logo-generator-skill/references/design_patterns.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/logo-generator-skill/references/webgl_backgrounds.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/logo-generator-skill/requirements.txt +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/logo-generator-skill/scripts/generate_showcase.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/logo-generator-skill/scripts/svg_to_png.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/SKILL.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/brainstorming/spec-document-reviewer-prompt.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/implementation/coding-plan-template.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/implementation/impl-guide-template.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/review/pr-review-template.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/review/review-checklist.md +0 -0
- {flowjet-2.0.8/docs/specs/templates → flowjet-2.0.10/src/flowjet/builtin_skills/platonic-coding/assets/specs}/rfc-template.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-architecture-design.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-conceptual-design.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-impl-interface-design.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-drafts-readme.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-impl-readme.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-platonic.yml +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-history.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-index.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-namings.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-standard.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/BRAINSTORM/brainstorm.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-code.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-create-guide.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-full.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-update-guide.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-validate-guide.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-plan-modular-specs.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-architecture.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-conceptual.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-impl-interface.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-scaffold.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-scan.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/REFERENCE.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/REVIEW/review-spec-compliance.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-check-compliance.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-check-taxonomy.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-history.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-index.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-namings.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-refine.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-validate-consistency.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-overview.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-1.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-2.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-3.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/SKILL.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/patterns/evidence-deep.json +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/patterns/evidence-first.json +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/patterns/idea-first.json +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/patterns/loop.json +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/patterns/paper-pipeline.json +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/patterns/rapid.json +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/patterns/rigor-first.json +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/patterns/stance-first.json +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/patterns/systematic-review.json +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/patterns/verify-loop.json +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/references/backends.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/references/handoff-ars-omr.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/references/handoff-omr-ars.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/references/routing.md +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/scripts/csljson_to_omr.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/builtin_skills/research-bootstrap/scripts/papers_index_to_csljson.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/__init__.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/__main__.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/agent.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/completion/__init__.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/completion/context.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/completion/engine.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/doctor_cmd.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/doctor_fix_cmd.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/serve_cmd.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/setup_cmd.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/shell/__init__.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/shell/_fj.zsh +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/shell/fj.bash +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/stream.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/threads.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/cli/tool_stream.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/__init__.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backends/__init__.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backends/direct.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backends/fake.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backends/isolation/admission.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backends/isolation/errors.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backends/isolation/mode_gate.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backends/isolation/pool.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/backends/isolation/request.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/bridges/nano/__init__.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/bridges/nano/adapter.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/events.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/identity.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/modes.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/protocol.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/core/tool_results.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/home.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/__init__.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/acp/__init__.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/acp/routes.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/http/__init__.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/http/auth.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/openai_compat/__init__.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/openai_compat/errors.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/openai_compat/projection.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/openai_compat/routes.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/openai_compat/schemas.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/openai_compat/service.py +0 -0
- {flowjet-2.0.8 → flowjet-2.0.10}/src/flowjet/server/openai_compat/store.py +0 -0
|
@@ -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.
|
|
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.
|
|
26
|
-
Requires-Dist: soothe<2.0.0,>=1.0
|
|
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 (`/
|
|
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 /
|
|
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.
|
|
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
|
-
#
|
|
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
|
-
|
|
33
|
-
|
|
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+
|
|
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
|
|
91
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
|
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
|
|
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)
|
|
19
|
-
|
|
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 ``
|
|
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
|
-
#:
|
|
51
|
-
#:
|
|
52
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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) ->
|
|
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
|
-
|
|
94
|
-
|
|
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
|
+
)
|