flowjet 2.0.4__tar.gz → 2.0.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.
Potentially problematic release.
This version of flowjet might be problematic. Click here for more details.
- {flowjet-2.0.4 → flowjet-2.0.6}/CHANGELOG.md +42 -3
- {flowjet-2.0.4 → flowjet-2.0.6}/PKG-INFO +4 -4
- {flowjet-2.0.4 → flowjet-2.0.6}/README.md +1 -1
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/impl/IG-002-isolated-thread-pool-runtime.md +2 -2
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/server.md +2 -2
- {flowjet-2.0.4 → flowjet-2.0.6}/examples/README.md +2 -1
- {flowjet-2.0.4 → flowjet-2.0.6}/pyproject.toml +3 -3
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/cli.py +2 -33
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/completion/engine.py +2 -1
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/doctor_cmd.py +1 -1
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/setup_cmd.py +76 -17
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/bootstrap.py +14 -5
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/bridges/nano/mapping.py +78 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/tool_stream.py +29 -0
- flowjet-2.0.6/src/flowjet/home.py +130 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/__main__.py +5 -7
- flowjet-2.0.4/src/flowjet/core/home.py +0 -127
- {flowjet-2.0.4 → flowjet-2.0.6}/.gitignore +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/LICENSE +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/config/nano.yml +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/deploy/README.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/deploy/nano.yml +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/completion-design.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/drafts/README.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/drafts/api-spec.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/impl/IG-001-openai-compatible-server.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/impl/IG-003-production-isolation-hardening.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/impl/README.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/impl/TEST-COVERAGE-MATRIX.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/naming.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/RFC-001-openai-compatible-api.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/RFC-002-isolated-thread-pool-runtime.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/RFC-003-production-isolation-hardening.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/RFC-004-unified-package.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/rfc-history.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/rfc-index.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/rfc-namings.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/templates/architecture-design.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/templates/conceptual-design.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/templates/impl-interface-design.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/templates/rfc-standard.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/specs/templates/rfc-template.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/docs/upgrade.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/nano.yml +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/__init__.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/__main__.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/README.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/gws-drive/SKILL.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/gws-drive-upload/SKILL.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/logo-generator-skill/.env.example +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/logo-generator-skill/README.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/logo-generator-skill/SKILL.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/logo-generator-skill/assets/background_library.html +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/logo-generator-skill/assets/showcase_template.html +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/logo-generator-skill/references/background_styles.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/logo-generator-skill/references/design_patterns.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/logo-generator-skill/references/webgl_backgrounds.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/logo-generator-skill/requirements.txt +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/logo-generator-skill/scripts/generate_showcase.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/logo-generator-skill/scripts/svg_to_png.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/SKILL.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/brainstorming/spec-document-reviewer-prompt.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/implementation/coding-plan-template.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/implementation/impl-guide-template.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/review/pr-review-template.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/review/review-checklist.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/specs/rfc-template.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-architecture-design.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-conceptual-design.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-impl-interface-design.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-drafts-readme.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-impl-readme.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-platonic.yml +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-history.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-index.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-namings.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-standard.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/BRAINSTORM/brainstorm.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-code.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-create-guide.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-full.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-update-guide.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-validate-guide.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-plan-modular-specs.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-architecture.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-conceptual.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-impl-interface.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-scaffold.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-scan.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/REFERENCE.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/REVIEW/review-spec-compliance.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-check-compliance.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-check-taxonomy.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-history.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-index.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-namings.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-refine.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-validate-consistency.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-overview.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-1.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-2.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-3.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/SKILL.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/evidence-deep.json +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/evidence-first.json +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/idea-first.json +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/loop.json +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/paper-pipeline.json +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/rapid.json +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/rigor-first.json +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/stance-first.json +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/systematic-review.json +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/verify-loop.json +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/references/backends.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/references/handoff-ars-omr.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/references/handoff-omr-ars.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/references/routing.md +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/scripts/csljson_to_omr.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/builtin_skills/research-bootstrap/scripts/papers_index_to_csljson.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/__init__.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/__main__.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/agent.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/completion/__init__.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/completion/context.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/doctor_fix_cmd.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/progress.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/serve_cmd.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/shell/__init__.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/shell/_fj.zsh +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/shell/fj.bash +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/stream.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/threads.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/cli/tool_stream.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/__init__.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backend.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/__init__.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/direct.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/fake.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/isolation/__init__.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/isolation/adapter.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/isolation/admission.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/isolation/backend.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/isolation/errors.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/isolation/mode_gate.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/isolation/pool.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/isolation/request.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/backends/isolation/workspace.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/bridges/nano/__init__.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/bridges/nano/adapter.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/bridges/nano/backend.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/events.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/identity.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/modes.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/protocol.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/core/tool_results.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/__init__.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/acp/__init__.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/acp/agent.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/acp/routes.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/config.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/http/__init__.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/http/app.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/http/auth.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/openai_compat/__init__.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/openai_compat/errors.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/openai_compat/projection.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/openai_compat/routes.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/openai_compat/schemas.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/openai_compat/service.py +0 -0
- {flowjet-2.0.4 → flowjet-2.0.6}/src/flowjet/server/openai_compat/store.py +0 -0
|
@@ -10,12 +10,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
-
## [2.0.
|
|
13
|
+
## [2.0.6] — 2026-09-21
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **Bump soothe runtime to the latest.** `soothe` `1.0.12 → 1.0.14` and
|
|
18
|
+
`soothe-nano` `1.2.28 → 1.3.0`. The `<1.3.0` cap on `soothe-nano` was lifted
|
|
19
|
+
to `<1.4.0` because `soothe 1.0.14` now requires `soothe-nano>=1.3.0`. The
|
|
20
|
+
upgrade adds one transitive dependency, `langchain-typesafe`. All FlowJet
|
|
21
|
+
import paths into soothe/soothe-nano (`SootheConfig`, `create_nano_agent`,
|
|
22
|
+
`create_dual_mode_nano_agent`, `create_soothe_agent`, `resolve_checkpointer`,
|
|
23
|
+
`register_builtin_skill_root`, `FrameworkFilesystem`, `diagnose`, etc.) are
|
|
24
|
+
unchanged and continue to resolve.
|
|
25
|
+
|
|
26
|
+
## [2.0.5] — 2026-09-20
|
|
14
27
|
|
|
15
28
|
### Changed
|
|
16
29
|
|
|
17
|
-
-
|
|
18
|
-
|
|
30
|
+
- **Config moved off `~/.soothe` — `FLOWJET_HOME` is now the authority.**
|
|
31
|
+
The config file resolves to `$FLOWJET_HOME/config/nano.yml` (default
|
|
32
|
+
`~/.flowjet/config/nano.yml`) instead of `$SOOTHE_HOME/config/nano.yml`, so an
|
|
33
|
+
exported `SOOTHE_HOME` can no longer drag flowjet back onto `~/.soothe`.
|
|
34
|
+
`SOOTHE_HOME` itself is left untouched: CLI data (thread pins, locks,
|
|
35
|
+
completion history), logs, agents, plugins and the sqlite databases still live
|
|
36
|
+
there. Upgrades keep working — an existing `~/.soothe/config/nano.yml` (plus a
|
|
37
|
+
sibling `soothe.yml`) is copied into `$FLOWJET_HOME/config/` **once**, on first
|
|
38
|
+
run, and the originals are left in place, so the move is reversible.
|
|
39
|
+
- **`fj setup` no longer aborts when the LLM endpoint is unreachable.** A failed
|
|
40
|
+
or empty `/models` probe (connection refused, HTTP error, unresolved
|
|
41
|
+
`${ENV_VAR}`) now prints a `warning:` and falls through to manual model entry
|
|
42
|
+
— offering the models already saved for the selected provider, or accepting a
|
|
43
|
+
typed model id. The provider and router profile are still written, so a config
|
|
44
|
+
can be initialised before the model server is running; the run ends with a note
|
|
45
|
+
to re-check via `fj doctor`.
|
|
46
|
+
|
|
47
|
+
## [2.0.4] — 2026-09-19
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- **`fj -V` is instant (1.78s → 0.06s).** It paid the full `soothe_nano` +
|
|
52
|
+
`flowjet.core.bootstrap` import cost on every invocation just to print a
|
|
53
|
+
version string. The stdlib-only logging helpers moved out of `agent.py` into
|
|
54
|
+
`cli.py`, `main()` parses args before setting up logging so `-V`/`-h` exit
|
|
55
|
+
first, and the `-c/--config` help text now resolves lazily.
|
|
56
|
+
(`agent.py` keeps thin delegation wrappers, so existing imports and
|
|
57
|
+
monkeypatching are unaffected.)
|
|
19
58
|
|
|
20
59
|
## [2.0.3] — 2026-09-18
|
|
21
60
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: flowjet
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.6
|
|
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.4.0,>=1.3.0
|
|
26
|
+
Requires-Dist: soothe<2.0.0,>=1.0.14
|
|
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'
|
|
@@ -75,7 +75,7 @@ export OPENAI_API_KEY=sk-...
|
|
|
75
75
|
fj summarize README.md
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
`fj setup` writes `~/.
|
|
78
|
+
`fj setup` writes `$FLOWJET_HOME/config/nano.yml` (default `~/.flowjet/config/nano.yml`). Without it, FlowJet falls back to `OPENAI_API_KEY` or `ANTHROPIC_API_KEY`.
|
|
79
79
|
|
|
80
80
|
Not sure your machine is ready? `fj doctor` (add `--deep`, `--live-llm`).
|
|
81
81
|
|
|
@@ -31,7 +31,7 @@ export OPENAI_API_KEY=sk-...
|
|
|
31
31
|
fj summarize README.md
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
`fj setup` writes `~/.
|
|
34
|
+
`fj setup` writes `$FLOWJET_HOME/config/nano.yml` (default `~/.flowjet/config/nano.yml`). Without it, FlowJet falls back to `OPENAI_API_KEY` or `ANTHROPIC_API_KEY`.
|
|
35
35
|
|
|
36
36
|
Not sure your machine is ready? `fj doctor` (add `--deep`, `--live-llm`).
|
|
37
37
|
|
|
@@ -193,8 +193,8 @@ graph; abnormal turns recycle it before the worker is made available.
|
|
|
193
193
|
|
|
194
194
|
| Env | Settings field | Default |
|
|
195
195
|
|-----|----------------|---------|
|
|
196
|
-
| `FLOWJET_HOME` | `home` | `~/.flowjet` |
|
|
197
|
-
| `FLOWJET_NANO_CONFIG` | `nano_config` | unset → `$
|
|
196
|
+
| `FLOWJET_HOME` | `home` | `~/.flowjet` (config file + workspaces) |
|
|
197
|
+
| `FLOWJET_NANO_CONFIG` | `nano_config` | unset → `$FLOWJET_HOME/config/nano.yml` |
|
|
198
198
|
| `FLOWJET_THREAD_POOL_MIN` | `thread_pool_min` | `2` |
|
|
199
199
|
| `FLOWJET_THREAD_POOL_MAX` | `thread_pool_max` | `8` |
|
|
200
200
|
| `FLOWJET_THREAD_POOL_IDLE_TIMEOUT` | `thread_pool_idle_timeout` | `300` |
|
|
@@ -72,8 +72,8 @@ with client.responses.stream(
|
|
|
72
72
|
| `FLOWJET_MODELS` | `default` | Comma-separated logical model ids |
|
|
73
73
|
| `FLOWJET_HOST` | `::` | Bind host (dual-stack IPv4/IPv6) |
|
|
74
74
|
| `FLOWJET_PORT` | `8618` | Bind port |
|
|
75
|
-
| `FLOWJET_HOME` | `~/.flowjet` | Root for per-session workspaces |
|
|
76
|
-
| `FLOWJET_NANO_CONFIG` | unset | Optional `nano.yml` (else `$
|
|
75
|
+
| `FLOWJET_HOME` | `~/.flowjet` | Root for the config file (`$FLOWJET_HOME/config/nano.yml`) and per-session workspaces |
|
|
76
|
+
| `FLOWJET_NANO_CONFIG` | unset | Optional `nano.yml` (else `$FLOWJET_HOME/config/nano.yml`) |
|
|
77
77
|
| `FLOWJET_THREAD_POOL_MIN` | `2` | Min isolation worker threads |
|
|
78
78
|
| `FLOWJET_THREAD_POOL_MAX` | `8` | Max isolation worker threads |
|
|
79
79
|
| `FLOWJET_THREAD_POOL_IDLE_TIMEOUT` | `300` | Scaled-worker idle exit seconds (`0` = never) |
|
|
@@ -35,7 +35,8 @@ make examples-e2e
|
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
The server uses soothe-nano with thread-pool isolation. Nano loads
|
|
38
|
-
|
|
38
|
+
`$FLOWJET_HOME/config/nano.yml` (default `~/.flowjet/config/nano.yml`, or
|
|
39
|
+
`FLOWJET_NANO_CONFIG`) and its active router profile.
|
|
39
40
|
|
|
40
41
|
## Environment
|
|
41
42
|
|
|
@@ -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.6"
|
|
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" }
|
|
@@ -31,8 +31,8 @@ classifiers = [
|
|
|
31
31
|
dependencies = [
|
|
32
32
|
# Default CLI runtime (FLOWJET_BACKEND=soothe). soothe is a host layer over
|
|
33
33
|
# soothe-nano, so nano stays pinned explicitly: soothe only caps it at <2.
|
|
34
|
-
"soothe>=1.0.
|
|
35
|
-
"soothe-nano>=1.
|
|
34
|
+
"soothe>=1.0.14,<2.0.0",
|
|
35
|
+
"soothe-nano>=1.3.0,<1.4.0",
|
|
36
36
|
]
|
|
37
37
|
|
|
38
38
|
[project.optional-dependencies]
|
|
@@ -44,20 +44,14 @@ class _CompactConsoleFormatter(logging.Formatter):
|
|
|
44
44
|
return super().format(record)
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
_FILE_LOGGING_INSTALLED = False
|
|
48
|
-
|
|
49
|
-
|
|
50
47
|
def configure_cli_logging(*, verbose: bool = False) -> None:
|
|
51
|
-
"""Quiet the console for one-shot CLI use
|
|
48
|
+
"""Quiet the console for one-shot CLI use.
|
|
52
49
|
|
|
53
50
|
- Opt out of ``browser_use`` import-time root logger setup when unset.
|
|
54
51
|
- Remove existing root stream handlers (stderr/stdout) so init INFO
|
|
55
52
|
lines do not interleave with the agent answer.
|
|
56
53
|
- Prevent ``lastResort`` traceback dumps from soothe tool failures.
|
|
57
54
|
- When ``verbose``, show WARNING+ as single-line messages on stderr.
|
|
58
|
-
- Attach a rotating file handler at ``<FLOWJET_HOME>/logs/cli.log`` so
|
|
59
|
-
every CLI run is recorded by default (idempotent across re-invocations
|
|
60
|
-
within the same process, e.g. tests).
|
|
61
55
|
"""
|
|
62
56
|
os.environ.setdefault(_BROWSER_USE_SETUP_LOGGING, "false")
|
|
63
57
|
_remove_root_console_handlers()
|
|
@@ -75,25 +69,6 @@ def configure_cli_logging(*, verbose: bool = False) -> None:
|
|
|
75
69
|
if root.level < logging.WARNING:
|
|
76
70
|
root.setLevel(logging.WARNING)
|
|
77
71
|
|
|
78
|
-
_ensure_cli_file_logging()
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def _ensure_cli_file_logging() -> None:
|
|
82
|
-
"""Attach the rotating file handler for CLI runs (idempotent)."""
|
|
83
|
-
global _FILE_LOGGING_INSTALLED
|
|
84
|
-
if _FILE_LOGGING_INSTALLED:
|
|
85
|
-
return
|
|
86
|
-
try:
|
|
87
|
-
from flowjet.core.home import cli_log_path, ensure_flowjet_home
|
|
88
|
-
|
|
89
|
-
ensure_flowjet_home()
|
|
90
|
-
from soothe_nano.logging.setup import setup_logging
|
|
91
|
-
|
|
92
|
-
setup_logging(log_file=str(cli_log_path()), extra_logger_names=("flowjet",))
|
|
93
|
-
_FILE_LOGGING_INSTALLED = True
|
|
94
|
-
except Exception: # pragma: no cover - logging must never break the CLI
|
|
95
|
-
pass
|
|
96
|
-
|
|
97
72
|
|
|
98
73
|
def silence_after_plugins(*, verbose: bool = False) -> None:
|
|
99
74
|
"""Re-apply quieting after plugin imports (belt-and-suspenders)."""
|
|
@@ -321,7 +296,7 @@ class _HelpFormatter(argparse.RawDescriptionHelpFormatter):
|
|
|
321
296
|
|
|
322
297
|
|
|
323
298
|
def _default_config_help() -> str:
|
|
324
|
-
from flowjet.
|
|
299
|
+
from flowjet.home import default_config_path
|
|
325
300
|
|
|
326
301
|
return f"Alternate nano.yml (default: {default_config_path()})"
|
|
327
302
|
|
|
@@ -687,12 +662,6 @@ def main_follow(argv: list[str] | None = None) -> int:
|
|
|
687
662
|
|
|
688
663
|
def main(argv: list[str] | None = None) -> int:
|
|
689
664
|
try:
|
|
690
|
-
# Align SOOTHE_HOME with FLOWJET_HOME before any soothe import so
|
|
691
|
-
# config, data, and logs all land under ~/.flowjet. Must run first:
|
|
692
|
-
# soothe_sdk.paths binds SOOTHE_HOME at import time.
|
|
693
|
-
from flowjet.core.home import ensure_flowjet_home
|
|
694
|
-
|
|
695
|
-
ensure_flowjet_home()
|
|
696
665
|
# Parse first so ``-V`` / ``-h`` / ``--help`` exit before we touch the
|
|
697
666
|
# logging stack (and the heavy ``flowjet.cli.agent`` import it used to
|
|
698
667
|
# drag in). Version/help must stay instant.
|
|
@@ -419,13 +419,14 @@ def _split_complete_argv(raw: list[str]) -> tuple[list[str], list[str]]:
|
|
|
419
419
|
def run_complete(argv: list[str] | None = None) -> int:
|
|
420
420
|
"""Hidden shell protocol: print one candidate per stdout line."""
|
|
421
421
|
from flowjet.cli.cli import resolve_cli_prog
|
|
422
|
+
from flowjet.home import default_config_path
|
|
422
423
|
|
|
423
424
|
parser = argparse.ArgumentParser(prog=f"{resolve_cli_prog()} __complete", add_help=True)
|
|
424
425
|
parser.add_argument(
|
|
425
426
|
"-c",
|
|
426
427
|
"--config",
|
|
427
428
|
metavar="PATH",
|
|
428
|
-
help="nano.yml path (default:
|
|
429
|
+
help=f"nano.yml path (default: {default_config_path()})",
|
|
429
430
|
)
|
|
430
431
|
parser.add_argument(
|
|
431
432
|
"--no-llm",
|
|
@@ -46,8 +46,8 @@ REFERENCE_SKILLS: tuple[dict[str, Any], ...] = (
|
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def _build_parser() -> argparse.ArgumentParser:
|
|
49
|
-
from flowjet.cli.agent import default_config_path
|
|
50
49
|
from flowjet.cli.cli import resolve_cli_prog
|
|
50
|
+
from flowjet.home import default_config_path
|
|
51
51
|
|
|
52
52
|
parser = argparse.ArgumentParser(
|
|
53
53
|
prog=f"{resolve_cli_prog()} doctor",
|
|
@@ -21,7 +21,7 @@ except ImportError: # pragma: no cover - Windows
|
|
|
21
21
|
import yaml
|
|
22
22
|
from soothe_nano.config import _ENV_VAR_RE, _resolve_env
|
|
23
23
|
|
|
24
|
-
from flowjet.
|
|
24
|
+
from flowjet.home import default_config_path, ensure_flowjet_config
|
|
25
25
|
|
|
26
26
|
DEFAULT_PROVIDER_NAME = "local"
|
|
27
27
|
DEFAULT_NEW_PROVIDER_NAME = "flowjet-default"
|
|
@@ -65,6 +65,10 @@ def run_setup(config_path: str | None = None) -> int:
|
|
|
65
65
|
|
|
66
66
|
def _run_setup(config_path: str | None = None) -> int:
|
|
67
67
|
path = Path(config_path).expanduser() if config_path else default_config_path()
|
|
68
|
+
if config_path is None:
|
|
69
|
+
# ``fj setup`` bypasses ``load_config``, so it pulls in a ``~/.soothe/config``
|
|
70
|
+
# copy itself before reading the provider list.
|
|
71
|
+
ensure_flowjet_config()
|
|
68
72
|
existing = _load_existing_config(path)
|
|
69
73
|
providers = _list_providers(existing)
|
|
70
74
|
_, seed_model = _seed_from_config(existing)
|
|
@@ -100,27 +104,39 @@ def _run_setup(config_path: str | None = None) -> int:
|
|
|
100
104
|
api_key = _prompt_secret_with_default("API key", api_key_default)
|
|
101
105
|
|
|
102
106
|
sys.stdout.write("Fetching available models...\n")
|
|
107
|
+
models: list[str] = []
|
|
108
|
+
fetch_issue: str | None = None
|
|
103
109
|
try:
|
|
104
110
|
resolved_endpoint = resolve_config_value(endpoint, field="API endpoint")
|
|
105
111
|
resolved_api_key = resolve_config_value(api_key, field="API key")
|
|
106
|
-
models = fetch_models(resolved_endpoint, resolved_api_key)
|
|
107
112
|
except RuntimeError as exc:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
113
|
+
fetch_issue = str(exc)
|
|
114
|
+
else:
|
|
115
|
+
try:
|
|
116
|
+
models = fetch_models(resolved_endpoint, resolved_api_key)
|
|
117
|
+
except RuntimeError as exc:
|
|
118
|
+
fetch_issue = str(exc)
|
|
119
|
+
if not models and fetch_issue is None:
|
|
120
|
+
fetch_issue = f"{resolved_endpoint} returned no models"
|
|
121
|
+
|
|
122
|
+
if fetch_issue:
|
|
123
|
+
# An unreachable endpoint is not fatal: save the provider anyway so the
|
|
124
|
+
# user can start the model server later and pick a model by hand now.
|
|
125
|
+
sys.stderr.write(f"warning: could not list models ({fetch_issue})\n")
|
|
126
|
+
sys.stderr.write("warning: continuing without a model list\n")
|
|
127
|
+
|
|
128
|
+
if models:
|
|
129
|
+
preferred = seed_model
|
|
130
|
+
if selected:
|
|
131
|
+
selected_models = selected.get("models")
|
|
132
|
+
if isinstance(selected_models, list) and selected_models:
|
|
133
|
+
preferred = str(selected_models[0])
|
|
134
|
+
if preferred and preferred in models:
|
|
135
|
+
models = [preferred] + [m for m in models if m != preferred]
|
|
136
|
+
model = choose_model_interactive(models)
|
|
137
|
+
else:
|
|
138
|
+
model = _prompt_model_with_fallback(_fallback_model_candidates(selected, seed_model))
|
|
122
139
|
|
|
123
|
-
model = choose_model_interactive(models)
|
|
124
140
|
if not model:
|
|
125
141
|
sys.stderr.write("setup cancelled\n")
|
|
126
142
|
return 1
|
|
@@ -139,6 +155,10 @@ def _run_setup(config_path: str | None = None) -> int:
|
|
|
139
155
|
sys.stdout.write(f"Saved config to: {path}\n")
|
|
140
156
|
sys.stdout.write(f"Active profile: {profile}\n")
|
|
141
157
|
sys.stdout.write(f"Configured model: {provider_name}:{model}\n")
|
|
158
|
+
if fetch_issue:
|
|
159
|
+
sys.stdout.write(
|
|
160
|
+
"Note: model list was not verified; start the endpoint and run 'fj doctor' to confirm.\n"
|
|
161
|
+
)
|
|
142
162
|
return 0
|
|
143
163
|
|
|
144
164
|
|
|
@@ -464,6 +484,45 @@ def _parse_model_filter(choice: str) -> str:
|
|
|
464
484
|
return raw
|
|
465
485
|
|
|
466
486
|
|
|
487
|
+
def _fallback_model_candidates(selected: dict[str, Any], seed_model: str | None) -> list[str]:
|
|
488
|
+
"""Models already known from config, used when the endpoint cannot be queried."""
|
|
489
|
+
candidates: list[str] = []
|
|
490
|
+
if selected:
|
|
491
|
+
models = selected.get("models")
|
|
492
|
+
if isinstance(models, list):
|
|
493
|
+
candidates.extend(str(item) for item in models if str(item).strip())
|
|
494
|
+
if seed_model and seed_model not in candidates:
|
|
495
|
+
candidates.insert(0, seed_model)
|
|
496
|
+
return candidates
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def _prompt_model_with_fallback(candidates: list[str]) -> str | None:
|
|
500
|
+
"""Ask for a model id directly when the endpoint has no model list to pick from."""
|
|
501
|
+
default = candidates[0] if candidates else DEFAULT_MODEL
|
|
502
|
+
if candidates:
|
|
503
|
+
sys.stdout.write("Models saved for this provider:\n")
|
|
504
|
+
for idx, name in enumerate(candidates, start=1):
|
|
505
|
+
sys.stdout.write(f" {idx}. {name}\n")
|
|
506
|
+
label = f"Model name or number 1-{len(candidates)}"
|
|
507
|
+
else:
|
|
508
|
+
label = "Model name"
|
|
509
|
+
|
|
510
|
+
while True:
|
|
511
|
+
try:
|
|
512
|
+
value = input(f"{label} [{default}]: ").strip()
|
|
513
|
+
except EOFError:
|
|
514
|
+
return default
|
|
515
|
+
if not value:
|
|
516
|
+
return default
|
|
517
|
+
if value.isdigit() and candidates:
|
|
518
|
+
idx = int(value)
|
|
519
|
+
if 1 <= idx <= len(candidates):
|
|
520
|
+
return candidates[idx - 1]
|
|
521
|
+
sys.stdout.write(f"Invalid number; enter 1-{len(candidates)} or a model name.\n")
|
|
522
|
+
continue
|
|
523
|
+
return value
|
|
524
|
+
|
|
525
|
+
|
|
467
526
|
def _load_existing_config(path: Path) -> dict[str, Any]:
|
|
468
527
|
if not path.is_file():
|
|
469
528
|
return {}
|
|
@@ -18,7 +18,7 @@ from contextlib import asynccontextmanager
|
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
from typing import Any
|
|
20
20
|
|
|
21
|
-
from soothe_nano.config import
|
|
21
|
+
from soothe_nano.config import SootheConfig
|
|
22
22
|
from soothe_nano.resolve import resolve_checkpointer
|
|
23
23
|
|
|
24
24
|
from flowjet.core.backend import Backend, resolve_backend
|
|
@@ -29,6 +29,8 @@ from flowjet.core.modes import (
|
|
|
29
29
|
SurfaceProfile,
|
|
30
30
|
require_mode,
|
|
31
31
|
)
|
|
32
|
+
from flowjet.home import default_config_path as _flowjet_config_path
|
|
33
|
+
from flowjet.home import ensure_flowjet_config
|
|
32
34
|
|
|
33
35
|
logger = logging.getLogger(__name__)
|
|
34
36
|
|
|
@@ -75,8 +77,8 @@ def register_flowjet_builtin_skills() -> None:
|
|
|
75
77
|
|
|
76
78
|
|
|
77
79
|
def default_config_path() -> Path:
|
|
78
|
-
"""Return
|
|
79
|
-
return
|
|
80
|
+
"""Return ``$FLOWJET_HOME/config/nano.yml`` (default ``~/.flowjet/config/nano.yml``)."""
|
|
81
|
+
return _flowjet_config_path()
|
|
80
82
|
|
|
81
83
|
|
|
82
84
|
def load_config(
|
|
@@ -93,14 +95,21 @@ def load_config(
|
|
|
93
95
|
|
|
94
96
|
Resolution order:
|
|
95
97
|
1. Explicit ``config_path``
|
|
96
|
-
2.
|
|
98
|
+
2. ``$FLOWJET_HOME / config / nano.yml`` (default ``~/.flowjet/config/nano.yml``)
|
|
97
99
|
3. Zero-config ``SootheConfig()`` from ``OPENAI_API_KEY`` / ``ANTHROPIC_API_KEY``
|
|
98
100
|
|
|
99
101
|
On the ``soothe`` backend, a sibling ``soothe.yml`` (host-owned keys) is
|
|
100
102
|
composed over ``nano.yml`` when present.
|
|
103
|
+
|
|
104
|
+
Without an explicit ``config_path``, a config found in ``~/.soothe/config`` is
|
|
105
|
+
copied into the flowjet home first — see :func:`flowjet.home.ensure_flowjet_config`.
|
|
101
106
|
"""
|
|
102
107
|
backend = backend or resolve_backend(Surface.CLI)
|
|
103
|
-
|
|
108
|
+
if config_path is None:
|
|
109
|
+
ensure_flowjet_config()
|
|
110
|
+
path = default_config_path()
|
|
111
|
+
else:
|
|
112
|
+
path = Path(config_path).expanduser()
|
|
104
113
|
if path.is_file():
|
|
105
114
|
return _load_config_file(path, backend)
|
|
106
115
|
if config_path is not None:
|
|
@@ -33,6 +33,13 @@ _SKIP_CUSTOM_TYPES = frozenset(
|
|
|
33
33
|
}
|
|
34
34
|
)
|
|
35
35
|
|
|
36
|
+
# Host (soothe) executor emits this as a ``mode=custom`` chunk carrying
|
|
37
|
+
# pre-parsed tool kwargs — a structured wire event that bypasses the fragile
|
|
38
|
+
# ``tool_call_chunks`` partial-JSON accumulation. Handled in
|
|
39
|
+
# ``iter_nano_runtime_events`` (not ``map_custom``) so it can yield a real
|
|
40
|
+
# ``ToolStarted`` and feed the accumulator's arg overlays.
|
|
41
|
+
_STREAM_TOOL_CALL_UPDATE = "soothe.stream.tool_call.update"
|
|
42
|
+
|
|
36
43
|
# Host (soothe) middleware surfaces as ``<Middleware>.<phase>`` graph updates.
|
|
37
44
|
# The CLI renders these through ``friendly_progress``, so the synthetic payloads
|
|
38
45
|
# below stay in the same ``soothe.*`` vocabulary as nano's custom events.
|
|
@@ -216,6 +223,10 @@ def map_custom(data: dict[str, Any]) -> Progress | None:
|
|
|
216
223
|
event_type = str(data.get("type") or "").strip()
|
|
217
224
|
if not event_type or event_type in _SKIP_CUSTOM_TYPES:
|
|
218
225
|
return None
|
|
226
|
+
if event_type == _STREAM_TOOL_CALL_UPDATE:
|
|
227
|
+
# Handled in ``iter_nano_runtime_events`` — yields a ToolStarted /
|
|
228
|
+
# Progress refresh with the pre-parsed args, never a generic Progress.
|
|
229
|
+
return None
|
|
219
230
|
if event_type.startswith("soothe.output."):
|
|
220
231
|
return None
|
|
221
232
|
short = event_type[7:] if event_type.startswith("soothe.") else event_type
|
|
@@ -232,6 +243,61 @@ def map_custom(data: dict[str, Any]) -> Progress | None:
|
|
|
232
243
|
return Progress(stage=str(stage), message=str(message or "Working"), raw=data)
|
|
233
244
|
|
|
234
245
|
|
|
246
|
+
def _wire_tool_call_update(
|
|
247
|
+
data: dict[str, Any],
|
|
248
|
+
*,
|
|
249
|
+
emitted_calls: set[str],
|
|
250
|
+
refreshed_calls: set[str],
|
|
251
|
+
tool_args: ToolCallArgAccumulator,
|
|
252
|
+
) -> list[RuntimeEvent]:
|
|
253
|
+
"""Convert a ``soothe.stream.tool_call.update`` into ToolStarted / Progress.
|
|
254
|
+
|
|
255
|
+
The host (soothe) executor pre-parses tool kwargs and emits them as a
|
|
256
|
+
structured wire event — an efficient transit path that does not depend on
|
|
257
|
+
``tool_call_chunks`` partial-JSON accumulation. This makes that path
|
|
258
|
+
first-class: a new call id yields ``ToolStarted`` (same as the
|
|
259
|
+
``messages`` path), and a repeat yields a ``Progress`` refresh so the
|
|
260
|
+
status line picks up the parsed args.
|
|
261
|
+
|
|
262
|
+
Args are also fed into ``tool_args`` overlays so the eventual
|
|
263
|
+
``ToolCompleted`` event carries them even if the ``messages``-mode chunks
|
|
264
|
+
never produced parseable JSON.
|
|
265
|
+
"""
|
|
266
|
+
tc_id = str(data.get("tool_call_id") or "").strip()
|
|
267
|
+
name = str(data.get("name") or "").strip()
|
|
268
|
+
args = data.get("args")
|
|
269
|
+
if not isinstance(args, dict):
|
|
270
|
+
args = {}
|
|
271
|
+
if not tc_id:
|
|
272
|
+
return []
|
|
273
|
+
# Seed the accumulator's overlay so ToolCompleted (messages path) sees
|
|
274
|
+
# the parsed args even if tool_call_chunks were empty or partial.
|
|
275
|
+
tool_args.seed_overlay(tc_id, name=name, args=args)
|
|
276
|
+
# Quiet tools (write_todos) are plan bookkeeping that already surfaces as
|
|
277
|
+
# step progress — never emit a ToolStarted for them.
|
|
278
|
+
if name in _QUIET_TOOLS:
|
|
279
|
+
return []
|
|
280
|
+
if tc_id in emitted_calls:
|
|
281
|
+
# Already started via the messages path; refresh the status line with
|
|
282
|
+
# the parsed args (Writing a.txt, not bare Writing).
|
|
283
|
+
if args and tc_id not in refreshed_calls:
|
|
284
|
+
refreshed_calls.add(tc_id)
|
|
285
|
+
return [
|
|
286
|
+
Progress(
|
|
287
|
+
stage="Tool",
|
|
288
|
+
message=tool_call_summary(name or "tool", args),
|
|
289
|
+
raw={
|
|
290
|
+
"type": "soothe.tool.started",
|
|
291
|
+
"tool": name,
|
|
292
|
+
"args": args,
|
|
293
|
+
},
|
|
294
|
+
)
|
|
295
|
+
]
|
|
296
|
+
return []
|
|
297
|
+
emitted_calls.add(tc_id)
|
|
298
|
+
return [ToolStarted(tool=name or "tool", call_id=tc_id, args=args or None)]
|
|
299
|
+
|
|
300
|
+
|
|
235
301
|
async def iter_nano_runtime_events(
|
|
236
302
|
agent: Any,
|
|
237
303
|
*,
|
|
@@ -284,6 +350,18 @@ async def iter_nano_runtime_events(
|
|
|
284
350
|
_ns, mode, data = chunk
|
|
285
351
|
|
|
286
352
|
if mode == "custom" and isinstance(data, dict):
|
|
353
|
+
# Host (soothe) wire tool-call update: pre-parsed args that
|
|
354
|
+
# bypass tool_call_chunks partial-JSON accumulation. Handle
|
|
355
|
+
# before map_custom so it yields a real ToolStarted.
|
|
356
|
+
if str(data.get("type") or "").strip() == _STREAM_TOOL_CALL_UPDATE:
|
|
357
|
+
for event in _wire_tool_call_update(
|
|
358
|
+
data,
|
|
359
|
+
emitted_calls=emitted_calls,
|
|
360
|
+
refreshed_calls=refreshed_calls,
|
|
361
|
+
tool_args=tool_args,
|
|
362
|
+
):
|
|
363
|
+
yield event
|
|
364
|
+
continue
|
|
287
365
|
mapped = map_custom(data)
|
|
288
366
|
if mapped:
|
|
289
367
|
yield mapped
|
|
@@ -244,3 +244,32 @@ class ToolCallArgAccumulator:
|
|
|
244
244
|
name = str(state.get("name") or "") or None
|
|
245
245
|
args = _merge_args(parse_partial_args(str(state.get("args_str") or "")), overlay)
|
|
246
246
|
return name, args
|
|
247
|
+
|
|
248
|
+
def seed_overlay(
|
|
249
|
+
self,
|
|
250
|
+
tool_call_id: str,
|
|
251
|
+
*,
|
|
252
|
+
name: str = "",
|
|
253
|
+
args: dict[str, Any] | None = None,
|
|
254
|
+
) -> None:
|
|
255
|
+
"""Seed name + parsed args for a call from a wire (non-chunk) source.
|
|
256
|
+
|
|
257
|
+
The host (soothe) executor emits ``soothe.stream.tool_call.update``
|
|
258
|
+
custom events with pre-parsed kwargs. Feeding them here lets
|
|
259
|
+
``ToolCompleted`` (messages path) resolve the name and args even when
|
|
260
|
+
``tool_call_chunks`` streamed empty/partial JSON — the overlay is the
|
|
261
|
+
fallback in ``_resolved_args`` / ``pop``.
|
|
262
|
+
"""
|
|
263
|
+
tc_id = str(tool_call_id or "").strip()
|
|
264
|
+
if not tc_id:
|
|
265
|
+
return
|
|
266
|
+
if name:
|
|
267
|
+
state = self._ensure(tc_id)
|
|
268
|
+
if not state.get("name"):
|
|
269
|
+
state["name"] = name
|
|
270
|
+
self._last_active_id = tc_id
|
|
271
|
+
if isinstance(args, dict) and args:
|
|
272
|
+
prev = self._overlays.get(tc_id) or {}
|
|
273
|
+
merged = _merge_args(prev, args)
|
|
274
|
+
if merged != prev:
|
|
275
|
+
self._overlays[tc_id] = merged
|