crow-cli 0.1.37__tar.gz → 0.1.38__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.
- {crow_cli-0.1.37 → crow_cli-0.1.38}/PKG-INFO +7 -5
- crow_cli-0.1.38/PLAN.md +93 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/README.md +4 -4
- crow_cli-0.1.38/TODO.md +73 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/pyproject.toml +4 -1
- crow_cli-0.1.38/scripts/e2e_image_store_live.py +177 -0
- crow_cli-0.1.38/scripts/e2e_postgres_live.py +204 -0
- crow_cli-0.1.38/src/crow_cli/agent/context.py +107 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/main.py +1 -2
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/memory.py +13 -4
- crow_cli-0.1.38/src/crow_cli/agent/prompt.py +507 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/react.py +38 -114
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/session.py +16 -117
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/tools.py +41 -110
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/cli/init_cmd.py +114 -3
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/config/config.py +10 -1
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/config/default/defaults.py +150 -8
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/editor/main.py +1 -1
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/memory/store.py +4 -3
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/memory/__init__.py +23 -6
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/memory/db.py +30 -11
- crow_cli-0.1.38/src/crow_cli/memory/fts.py +94 -0
- crow_cli-0.1.38/src/crow_cli/memory/image_store.py +170 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/memory/messages.py +18 -19
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/memory/models.py +5 -5
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/memory/reads.py +15 -25
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/memory/writes.py +16 -27
- crow_cli-0.1.38/tests/memory/test_db.py +53 -0
- crow_cli-0.1.38/tests/memory/test_image_store.py +136 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/memory/test_store.py +26 -25
- crow_cli-0.1.38/tests/unit/test_init_postgres.py +133 -0
- crow_cli-0.1.38/tests/unit/test_init_rustfs.py +59 -0
- crow_cli-0.1.38/tests/unit/test_prompt.py +280 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_react.py +14 -24
- crow_cli-0.1.38/tests/unit/test_session.py +56 -0
- crow_cli-0.1.38/tests/unit/test_system_prompt_skills.py +79 -0
- crow_cli-0.1.38/tests/unit/test_uv_project_enforcement.py +119 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/uv.lock +453 -4
- crow_cli-0.1.37/PLAN.md +0 -263
- crow_cli-0.1.37/TODO.md +0 -165
- crow_cli-0.1.37/src/crow_cli/agent/context.py +0 -109
- crow_cli-0.1.37/src/crow_cli/agent/prompt.py +0 -165
- crow_cli-0.1.37/tests/unit/test_session.py +0 -153
- crow_cli-0.1.37/tests/unit/test_system_prompt_skills.py +0 -26
- crow_cli-0.1.37/tests/unit/test_uv_project_enforcement.py +0 -159
- {crow_cli-0.1.37 → crow_cli-0.1.38}/.env.example +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/.github/workflows/build-binaries.yml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/.github/workflows/publish-crow-cli.yml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/.gitignore +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/.python-version +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/AGENTS.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/LICENSE.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/PYINSTALLER_BUILD.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/agent.json +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/crow-cli.spec +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/dev-crow-2.yaml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/CNAME +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/crow-cli-docs/getting-started/installation.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/crow-cli-docs/getting-started/quickstart.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/crow-cli-docs/index.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/crow-cli-docs/memory-tool-design.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/crow-cli-docs/myst.yml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/crow-cli-docs/styles/purple.css +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/crow-logo.svg +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/get-llamacpp-working.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/getting-started/installation.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/getting-started/quickstart.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/icon.ico +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/img/crow-icon-purple.svg +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/img/crow-logo-crop.png +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/img/icon.svg +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/img/whiteboard_capture.png +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/index.html +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/index.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/journal/03May2026.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/journal/19Feb2026.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/journal/20Feb2026.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/journal/22Feb2026.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/llm-wiki.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/myst.yml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/styles/purple.css +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/examples/mc_escher_loop.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/examples/quick_test.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/run_tests.sh +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/.python-version +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/README.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/SUCCESS.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/TODO.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/agent_client.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/echo_agent.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/fire_up_echo.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/fire_up_simple.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/pyproject.toml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/run_bridge.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/simple_agent.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/stdio_to_ws.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_acp_ws_client.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_agent_client.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_bridge.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_bridge_and_client.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_bridge_direct.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_client.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_date_terminal.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_echo_agent.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_fs_terminal.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_http_multisession.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_raw_stdio.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_simple.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_ws_client.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/two_sessions_same_question.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/uv.lock +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/async-react/.python-version +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/async-react/README.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/async-react/db_integrity_test.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/async-react/e2e_tool_integrity.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/async-react/persistence_integrity_test.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/async-react/pyproject.toml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/async-react/streaming_async_react.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/async-react/streaming_async_react_with_cancellation.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/async-react/test_usage.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/async-react/tool_call_integrity_test.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/async-react/uv.lock +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-acp-learning/README.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-acp-learning/agent.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-acp-learning/client.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-acp-learning/duet.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-acp-learning/echo_agent.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-acp-learning/fire_up_echo.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-acp-learning/gemini.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-acp-learning/pyproject.toml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-acp-learning/terminal_agent.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-acp-learning/test_acp.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-acp-learning/uv.lock +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-test/.python-version +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-test/README.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-test/graph.png +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-test/graph.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-test/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-test/pyproject.toml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-test/uv.lock +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/dirtree/.python-version +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/dirtree/README.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/dirtree/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/dirtree/pyproject.toml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/dirtree/uv.lock +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/00-architecture-overview.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/01-parallel-tool-calling.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/02-toad-integration.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/03-session-persistence.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/04-cancellation-and-state.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/05-loop-detection.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/06-finish-tool.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/07-python-repl.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/08-system-prompt-skills.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/09-session-continuity-and-compaction.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/AGENTS_OLD.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/ARCHITECTURE.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/CANCELLATION_IMPLEMENTATION.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/CANCELLATION_SANDBOX_COMPARISON.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/CANCELLATION_SUMMARY.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/CRITICAL_CLARIFICATIONS.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/CROW_SDK_ANALYSIS.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/EXTENSION_DISCUSSION.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/IMPLEMENTATION_PLAN.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/IMPLEMENTATION_SUMMARY.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/IMPLEMENTATION_SUMMARY_ACP_SESSIONS.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/INSTRUCTIONS_FOR_KIMI.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/PROJECT_VISION.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/PROMPT.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/PULSE_CHECK.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/REPL_GUIDE.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/TABLE_SCHEMA.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/THOUGHTS.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/check_acp_agent.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/conversation-wrapper-analysis.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/crow-agent-good-stuff.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/crow_agent_example.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/crow_inspect.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/database.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/00-file-editor-semantics-and-fastmcp.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/01-mcp-server-package-structure.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/02-hooks-realization-and-agent-core.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/03-where-we-are-now-and-whats-next.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/04-acp-mcp-configuration.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/04-hooks-as-first-class-citizens.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/05-hook-design-analysis.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/05-python-sdk-that-emerged.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/06-acp-native-sessions.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/07-extension-api-design.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/08-flask-extension-pattern-with-context-variables.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/09-git-commit-violation-last-testament.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/10-monorepo-refactor-to-avoid-cyclic-dependencies.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/11-acp-terminal-tool-swapping.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/12-hook-based-architecture-and-the-frameworkless-framework.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/13-acp-terminal-architecture.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/14-terminal-mcp-implementation.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/15-terminal-mcp-complete.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/16-fractal-filesystem-sandbox-parallel-tool-calling.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/17-composition-over-inheritance-extension-design.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/18-self-injection-the-god-mode-pattern.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/19-pluggy-the-missing-piece.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/20-terminal-tool-failure-bug-report.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/21-callback-architecture-multi-agent-bridges.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/22-the-agent-was-always-here.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/23-cancellation-solid-plan.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/24-async-tool-hanging-debug-thoughts.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/25-acp-client-terminal-integration.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/26-graceful-cancellation-streaming.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/llms-txt-and-agent-skills.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/fire_up_echo.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/kernel_kernel.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/merging-agents-guide.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/plans/acp-tool-enhancements.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/quick_test.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/research-findings.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/search.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/simple_crow_agent.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/streaming_async_react.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/streaming_async_react_with_db.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/streaming_react.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/terminal_demo.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/test_client_caps.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/test_crow_e2e.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/test_terminal_client.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/zai_tool_integration.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/.gitignore +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/.python-version +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/README.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/client.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/docs/essays/00-fractal-filesystem-sandbox-parallel-tool-calling.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/pyproject.toml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/src/fastmcp_test/__init__.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/src/fastmcp_test/server/__init__.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/src/fastmcp_test/server/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/src/fastmcp_test/tool/__init__.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/src/fastmcp_test/tool/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/uv.lock +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/.gitignore +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/.python-version +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/README.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/bench_colpali.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/bench_dual_model.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/bench_latency.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/lancedb_session.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/load_crow_db.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/multivector.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/pyproject.toml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/uv.lock +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/langfuse-test/.python-version +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/langfuse-test/README.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/langfuse-test/explore.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/langfuse-test/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/langfuse-test/pyproject.toml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/langfuse-test/uv.lock +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-dashscope/.python-version +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-dashscope/README.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-dashscope/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-dashscope/pyproject.toml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-dashscope/test_all_models_reasoning.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-dashscope/test_fix_correctness.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-dashscope/test_llamacpp_reasoning.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-dashscope/test_rc_empty.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-dashscope/test_reasoning_tool_transition.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-dashscope/test_reasoning_transition.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-dashscope/uv.lock +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-proxy/.python-version +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-proxy/README.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-proxy/config.yaml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-proxy/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-proxy/pyproject.toml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-proxy/uv.lock +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/murder-architecture.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/pathlib-test/.python-version +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/pathlib-test/README.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/pathlib-test/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/pathlib-test/pyproject.toml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/pathlib-test/src/pathlib_test/test.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/pathlib-test/uv.lock +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/pyinstaller-async-test/.python-version +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/pyinstaller-async-test/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/pyinstaller-async-test/pyproject.toml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/pyinstaller-async-test/test.spec +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/pyinstaller-async-test/uv.lock +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/.crow/compose.yaml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/.crow/config.yaml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/.crow/litellm/config.yaml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/.crow/searxng/settings.yml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/.gitignore +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/.python-version +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/AGENT_CLIENT_INTEGRATION.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/README.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/client.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_agent.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_e2e_test.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_fixtures/case_01_system_user_only.json +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_fixtures/case_02_reasoning_only.json +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_fixtures/case_03_content_only.json +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_fixtures/case_04_unexecuted_tools.json +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_fixtures/case_05_mid_conversation_all_responded.json +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_fixtures/case_06_partial_tool_responses.json +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_fixtures/case_07_complete_turn.json +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_fixtures/case_08_reasoning_ending.json +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_fixtures/case_09_content_ending.json +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_fixtures/chubby_full_conversation.jsonl +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/docs/AGENT_CLIENT_PLAN.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/docs/COMPACT_AND_SCHEMA_REWRITE.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/docs/STRATEGIC_PIVOT.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/generate_compact_fixtures.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/iterative_refinement.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/iterative_refinement_agent.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/iterative_refinement_mcp.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/multi_compact.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/orchestrator.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/pyproject.toml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/run_iterative_refinement_agent.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/run_repl_clients.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/scratch_db.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/task_description.yaml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/test_compact_fixtures.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/test_compact_one.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/test_compact_real.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/test_compact_run.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/test_drift_instrumentation.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/test_hook.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/test_iterative_refinement.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/test_multi_compact_client.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/test_refinement_agent.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/test_repl_client.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/uv.lock +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/yaml-configs/.python-version +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/yaml-configs/README.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/yaml-configs/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/yaml-configs/pyproject.toml +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/scripts/consolidate_history.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/scripts/e2e_memory_live.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/scripts/mcp_ram_repro.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/scripts/merge_live_into_migrated.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/scripts/migrate_taskmaster.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/scripts/migrate_v5.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/__init__.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/__init__.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/compact.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/hooks.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/llm.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/logger.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/mcp_client.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/model_routing.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/slash.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent_runner.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/cli/__init__.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/cli/install.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/cli/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/client/__init__.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/client/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/client/subagent.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/client/terminal.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/config/__init__.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/config/default/__init__.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/config/default/searxng_settings.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/__init__.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/editor/README.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/editor/__init__.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/memory/__init__.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/memory/client.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/memory/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/read/README.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/read/__init__.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/read/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/server/README.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/server/__init__.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/server/app.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/server/logger.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/server/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/task/__init__.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/task/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/terminal/README.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/terminal/__init__.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/terminal/backend.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/terminal/constants.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/terminal/logging_config.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/terminal/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/terminal/metadata.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/terminal/session.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/vision/__init__.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/vision/client.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/vision/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/web_fetch/README.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/web_fetch/__init__.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/web_fetch/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/web_search/README.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/web_search/__init__.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/web_search/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/write/README.md +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/write/__init__.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/write/main.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/memory/ids.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/__init__.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/analyze_payload_cache_invalidation.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/analyze_payload_deep.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/conftest.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/e2e/test_react_live.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/e2e/test_session_update_transmission.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/e2e/test_task_high_priority_preemption.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/e2e/test_task_mcp_launch.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/e2e/test_task_race_regression.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/integration/test_acp_http.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/integration/test_agent_init.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/integration/test_auth_validation.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/integration/test_react_loop_cancel_integrity.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/integration/test_react_loop_delegation_hold.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/integration/test_react_loop_tool_round.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/integration/test_reasoning_effort_react_loop.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/integration/test_server_stdio.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/test_edit.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/test_memory.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/test_memory_store.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/test_read.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/test_server.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/test_task_tool.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/test_terminal.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/test_vision.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/test_web_fetch.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/test_web_search.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/test_write.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/memory/test_migrate_taskmaster.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/memory/test_migrate_v5.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/memory/test_session_mcp_servers.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/memory/test_task_state.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_cli_fork_flags.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_cli_telemetry.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_client_terminal.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_compact.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_compact_last_messages_list_content.json +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_compact_last_messages_list_content.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_configure.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_fork.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_json_repair.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_mcp_client.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_model_override.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_model_routing.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_per_model_compact_threshold.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_reasoning_effort.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_request_logging.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_session_mcp_servers_wiring.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_stream_processing.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_subagent_parse_servers.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_task_tool_schema.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_usage_update.py +0 -0
- {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: crow-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.38
|
|
4
4
|
Summary: Add your description here
|
|
5
5
|
Project-URL: Homepage, https://github.com/crow-cli/crow-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/crow-cli/crow-cli
|
|
@@ -9,6 +9,7 @@ Project-URL: Documentation, https://github.com/crow-cli/crow-cli#readme
|
|
|
9
9
|
License-File: LICENSE.md
|
|
10
10
|
Requires-Python: >=3.14
|
|
11
11
|
Requires-Dist: agent-client-protocol[http]<0.13.0,>=0.12.0
|
|
12
|
+
Requires-Dist: boto3>=1.40.0
|
|
12
13
|
Requires-Dist: coolname>=2.2.0
|
|
13
14
|
Requires-Dist: directory-tree>=1.0.0
|
|
14
15
|
Requires-Dist: docker>=7.2.0
|
|
@@ -19,6 +20,7 @@ Requires-Dist: jinja2>=3.1.6
|
|
|
19
20
|
Requires-Dist: markdownify>=0.14.0
|
|
20
21
|
Requires-Dist: openai>=2.21.0
|
|
21
22
|
Requires-Dist: opencv-python>=4.13.0.92
|
|
23
|
+
Requires-Dist: psycopg[binary]>=3.1
|
|
22
24
|
Requires-Dist: pydantic>=2.12.5
|
|
23
25
|
Requires-Dist: pyyaml>=6.0.3
|
|
24
26
|
Requires-Dist: readabilipy>=0.3.0
|
|
@@ -122,15 +124,15 @@ The agent detects client capabilities (terminals, file read/write) and uses the
|
|
|
122
124
|
|
|
123
125
|
An ACP-native agent: a streaming ReAct loop with tool calling, cancellation, conversation compaction, and multimodal input. Provider and model configuration lives in `~/.agents/crow/config.yaml`.
|
|
124
126
|
|
|
125
|
-
### Persistence — sqlite
|
|
127
|
+
### Persistence — SQL memory (sqlite or PostgreSQL)
|
|
126
128
|
|
|
127
|
-
Sessions persist to a single sqlite
|
|
129
|
+
Sessions persist to a SQL database reached via the `db_uri` in `config.yaml`. The default is a single sqlite file (`~/.agents/crow/crow.db`, schema v5, WAL mode) with an FTS5 index for BM25 keyword search — zero setup, one file. Point `db_uri` at PostgreSQL instead (`postgresql+psycopg://…`; a `postgres` service ships in `compose.yaml`) and the same schema runs there for memory shared across machines — agents on any box see the same sessions, messages and task mailboxes. The dialect seams are isolated: keyword search is FTS5/BM25 on sqlite and tsvector/GIN on postgres behind the same `search_messages` contract, and the MCP read-only path is enforced per dialect (sqlite `mode=ro` file URI, postgres session `READ ONLY`). Images never live in the database: they are content-addressed (`<sha256hex><ext>`, so duplicates dedupe for free) in an image store and referenced by key; they are hydrated to base64 data URLs only when the conversation is sent to the LLM. The default store is the filesystem (`~/.agents/crow/images/`). Optionally, point `image_store.s3` in `config.yaml` at an S3 endpoint (a RustFS service ships in `compose.yaml`) and images go there instead — crow probes the endpoint once at startup and falls back to the filesystem when it is down, and reads always fall back to the filesystem too, so images stored before the switch keep working. The same database backs the memory API, exposed to agents as three tools:
|
|
128
130
|
|
|
129
131
|
- `list_sessions()` — sessions ordered by recent activity (who's working on what)
|
|
130
132
|
- `query_memory(query)` — find which session discussed something, across all sessions
|
|
131
133
|
- `query_session(session_id)` — read or search within one session (spans all of that session's agents)
|
|
132
134
|
|
|
133
|
-
This is what makes multi-agent delegation work: launch a worker, then read its thoughts from any other agent.
|
|
135
|
+
This is what makes multi-agent delegation work: launch a worker, then read its thoughts from any other agent. On sqlite there is no service to run — the file is the integration point. On PostgreSQL the server is the integration point, and every agent on the network shares one authoritative memory.
|
|
134
136
|
|
|
135
137
|
### Built-in MCP tool server
|
|
136
138
|
|
|
@@ -199,7 +201,7 @@ uv run pytest tests/e2e # live LLM calls (costs $)
|
|
|
199
201
|
src/crow_cli/ the agent — ACP server, ReAct loop, CLI
|
|
200
202
|
src/crow_cli/config/ config loading, defaults, overrides (shared by every layer)
|
|
201
203
|
src/crow_cli/mcp/ built-in MCP tool server (`crow-cli mcp`)
|
|
202
|
-
src/crow_cli/memory/ shared SQL persistence (sqlite default,
|
|
204
|
+
src/crow_cli/memory/ shared SQL persistence (sqlite default, PostgreSQL supported)
|
|
203
205
|
```
|
|
204
206
|
|
|
205
207
|
## License
|
crow_cli-0.1.38/PLAN.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# PLAN — PostgreSQL memory backend (same treatment as RustFS) — COMPLETE 2026-08-26
|
|
2
|
+
|
|
3
|
+
## **DO NOT ASK USER FOR FEEDBACK — THIS IS THE USER FEEDBACK.**
|
|
4
|
+
## **DO NOT ASK USER FOR NEXT STEPS — THESE ARE THE NEXT STEPS.**
|
|
5
|
+
|
|
6
|
+
Build/test gate (every phase boundary):
|
|
7
|
+
`./run_tests.sh tests/unit tests/memory tests/mcp tests/integration -q`
|
|
8
|
+
(rustfs sprint ended at 446 green on this gate; live-LLM e2e excluded.)
|
|
9
|
+
|
|
10
|
+
Trajectory: 1 → 2 → 3 → 4 → 5. Commit after each phase, Session-Id trailer.
|
|
11
|
+
|
|
12
|
+
## Phase 1 — mechanical portability — DONE 2026-08-26
|
|
13
|
+
|
|
14
|
+
1. models.py: `from sqlalchemy import JSON` (drop the sqlite dialect import).
|
|
15
|
+
2. db.py `_require_v5`: `sqlalchemy.inspect(engine).get_columns("agents")`
|
|
16
|
+
instead of `PRAGMA table_info` — portable, same fail-fast on v4 DBs.
|
|
17
|
+
3. pyproject: `psycopg[binary]` dep; `uv --project . sync`.
|
|
18
|
+
- Verify: gate green (sqlite behavior untouched). Commit.
|
|
19
|
+
|
|
20
|
+
## Phase 2 — the FTS seam — DONE 2026-08-26
|
|
21
|
+
|
|
22
|
+
1. New `src/crow_cli/memory/fts.py`: `create_fts(conn, engine)`,
|
|
23
|
+
`insert_fts(conn, engine, row_id, agent_id, role, fork_idx, text)`,
|
|
24
|
+
`search_fts(conn, engine, query, limit) -> list[(rowid, rank)]`
|
|
25
|
+
(best-first, lower=better on BOTH backends — postgres negates ts_rank).
|
|
26
|
+
sqlite branch = exact current SQL; postgres branch =
|
|
27
|
+
`CREATE TABLE IF NOT EXISTS messages_fts (rowid BIGINT PRIMARY KEY,
|
|
28
|
+
tsv tsvector)` + GIN index; `to_tsvector('simple', :t)` insert;
|
|
29
|
+
`plainto_tsquery('simple', :q)` + `ts_rank` search.
|
|
30
|
+
2. db.py create_database → fts.create_fts (drop FTS_DDL constant).
|
|
31
|
+
3. writes.py add_message → fts.insert_fts.
|
|
32
|
+
4. reads.py search_messages → fts.search_fts (drop inline MATCH SQL).
|
|
33
|
+
- Verify: gate green — sqlite search tests pass unchanged. Commit.
|
|
34
|
+
|
|
35
|
+
## Phase 3 — MCP dialect awareness — DONE 2026-08-26
|
|
36
|
+
|
|
37
|
+
1. db.py: `get_ro_engine(db_uri)` — sqlite → `?mode=ro&uri=true` rewrite;
|
|
38
|
+
postgres → connect listener `SET SESSION CHARACTERISTICS AS TRANSACTION
|
|
39
|
+
READ ONLY`. Export from memory/__init__.
|
|
40
|
+
2. mcp/memory/store.py `_ro_engine`: keep sqlite file-existence check,
|
|
41
|
+
delegate engine creation to get_ro_engine.
|
|
42
|
+
- Verify: gate green + new unit tests (sqlite rewrite unchanged; postgres
|
|
43
|
+
branch registers the listener — engine creation needs no live server).
|
|
44
|
+
Commit.
|
|
45
|
+
|
|
46
|
+
## Phase 4 — init/compose/config (the rustfs treatment) — DONE 2026-08-26
|
|
47
|
+
|
|
48
|
+
1. defaults.py COMPOSE_YAML: postgres service — postgres:17-alpine,
|
|
49
|
+
`${POSTGRES_PORT}:5432`, POSTGRES_USER/PASSWORD/DB env refs,
|
|
50
|
+
pg_isready healthcheck, postgres_data volume.
|
|
51
|
+
2. init_cmd.py Step 2c (mirror 2b): --yes → install; YES_INSTALL_POSTGRES;
|
|
52
|
+
else Confirm.ask. env_vars: POSTGRES_PORT=5432, POSTGRES_USER=crow,
|
|
53
|
+
POSTGRES_DB=crow, POSTGRES_PASSWORD=<env or secrets.token_hex(16)>.
|
|
54
|
+
setup_postgres → db_uri = postgresql+psycopg://${POSTGRES_USER}:
|
|
55
|
+
${POSTGRES_PASSWORD}@localhost:${POSTGRES_PORT}/${POSTGRES_DB};
|
|
56
|
+
compose writer includes postgres; review + done panels reflect backend.
|
|
57
|
+
3. config.py apply_config_overrides: resolve_env_vars on db_uri.
|
|
58
|
+
4. CONFIG_YAML template: commented postgres db_uri example.
|
|
59
|
+
- Verify: tests/unit/test_init_postgres.py (mirror test_init_rustfs.py) +
|
|
60
|
+
gate green. Commit.
|
|
61
|
+
|
|
62
|
+
## Phase 5 — live e2e + docs — DONE 2026-08-26
|
|
63
|
+
|
|
64
|
+
1. scripts/e2e_postgres_live.py: ephemeral postgres:17-alpine container
|
|
65
|
+
(port 15432, wait pg_isready); create_database on the postgres URI;
|
|
66
|
+
add_message → search_messages round trip (FTS works, lower=better);
|
|
67
|
+
JSON data round trip byte-identical; claim_deliveries with 2 concurrent
|
|
68
|
+
claimers → exactly one wins; get_ro_engine SELECT ok / INSERT raises;
|
|
69
|
+
kill container in finally; print E2E-POSTGRES-OK.
|
|
70
|
+
2. README persistence section: postgres option + when to use it
|
|
71
|
+
(multi-machine state). memory/__init__.py docstring: both backends now.
|
|
72
|
+
- Verify: script passes against live container; gate green; commit.
|
|
73
|
+
|
|
74
|
+
## Evidence log
|
|
75
|
+
|
|
76
|
+
- Phase 1 (58061203, 2026-08-26): generic JSON + inspector v5 check +
|
|
77
|
+
psycopg 3.3.4 installed; gate 446 passed.
|
|
78
|
+
- Phase 2 (73823339): memory/fts.py seam; sqlite search behavior unchanged —
|
|
79
|
+
gate 446 passed.
|
|
80
|
+
- Phase 3 (80e72807): get_ro_engine dialect-aware; tests/memory/test_db.py
|
|
81
|
+
(sqlite write actually refused by OS); gate 450 passed.
|
|
82
|
+
- Phase 4 (0de9c278): compose service + wizard Step 2c + db_uri env
|
|
83
|
+
resolution; tests/unit/test_init_postgres.py (5 tests); rendered compose
|
|
84
|
+
passes `docker compose config` with live .env interpolation; gate 455.
|
|
85
|
+
- Phase 5 (this commit): scripts/e2e_postgres_live.py → E2E-POSTGRES-OK
|
|
86
|
+
against a REAL postgres:17-alpine container: schema+GIN index created,
|
|
87
|
+
tsvector search round trip (lower=better), JSON round trip identical,
|
|
88
|
+
2 concurrent claimers → exactly one delivery claimed, RO engine SELECT ok
|
|
89
|
+
/ INSERT refused by the server. README persistence section rewritten.
|
|
90
|
+
Gate 455 passed.
|
|
91
|
+
|
|
92
|
+
Sprint complete: db_uri picks the backend — postgres config → shared
|
|
93
|
+
postgres; absent → sqlite + local FS, exactly as before.
|
|
@@ -93,15 +93,15 @@ The agent detects client capabilities (terminals, file read/write) and uses the
|
|
|
93
93
|
|
|
94
94
|
An ACP-native agent: a streaming ReAct loop with tool calling, cancellation, conversation compaction, and multimodal input. Provider and model configuration lives in `~/.agents/crow/config.yaml`.
|
|
95
95
|
|
|
96
|
-
### Persistence — sqlite
|
|
96
|
+
### Persistence — SQL memory (sqlite or PostgreSQL)
|
|
97
97
|
|
|
98
|
-
Sessions persist to a single sqlite
|
|
98
|
+
Sessions persist to a SQL database reached via the `db_uri` in `config.yaml`. The default is a single sqlite file (`~/.agents/crow/crow.db`, schema v5, WAL mode) with an FTS5 index for BM25 keyword search — zero setup, one file. Point `db_uri` at PostgreSQL instead (`postgresql+psycopg://…`; a `postgres` service ships in `compose.yaml`) and the same schema runs there for memory shared across machines — agents on any box see the same sessions, messages and task mailboxes. The dialect seams are isolated: keyword search is FTS5/BM25 on sqlite and tsvector/GIN on postgres behind the same `search_messages` contract, and the MCP read-only path is enforced per dialect (sqlite `mode=ro` file URI, postgres session `READ ONLY`). Images never live in the database: they are content-addressed (`<sha256hex><ext>`, so duplicates dedupe for free) in an image store and referenced by key; they are hydrated to base64 data URLs only when the conversation is sent to the LLM. The default store is the filesystem (`~/.agents/crow/images/`). Optionally, point `image_store.s3` in `config.yaml` at an S3 endpoint (a RustFS service ships in `compose.yaml`) and images go there instead — crow probes the endpoint once at startup and falls back to the filesystem when it is down, and reads always fall back to the filesystem too, so images stored before the switch keep working. The same database backs the memory API, exposed to agents as three tools:
|
|
99
99
|
|
|
100
100
|
- `list_sessions()` — sessions ordered by recent activity (who's working on what)
|
|
101
101
|
- `query_memory(query)` — find which session discussed something, across all sessions
|
|
102
102
|
- `query_session(session_id)` — read or search within one session (spans all of that session's agents)
|
|
103
103
|
|
|
104
|
-
This is what makes multi-agent delegation work: launch a worker, then read its thoughts from any other agent.
|
|
104
|
+
This is what makes multi-agent delegation work: launch a worker, then read its thoughts from any other agent. On sqlite there is no service to run — the file is the integration point. On PostgreSQL the server is the integration point, and every agent on the network shares one authoritative memory.
|
|
105
105
|
|
|
106
106
|
### Built-in MCP tool server
|
|
107
107
|
|
|
@@ -170,7 +170,7 @@ uv run pytest tests/e2e # live LLM calls (costs $)
|
|
|
170
170
|
src/crow_cli/ the agent — ACP server, ReAct loop, CLI
|
|
171
171
|
src/crow_cli/config/ config loading, defaults, overrides (shared by every layer)
|
|
172
172
|
src/crow_cli/mcp/ built-in MCP tool server (`crow-cli mcp`)
|
|
173
|
-
src/crow_cli/memory/ shared SQL persistence (sqlite default,
|
|
173
|
+
src/crow_cli/memory/ shared SQL persistence (sqlite default, PostgreSQL supported)
|
|
174
174
|
```
|
|
175
175
|
|
|
176
176
|
## License
|
crow_cli-0.1.38/TODO.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# TODO — PostgreSQL memory backend (same treatment as RustFS)
|
|
2
|
+
|
|
3
|
+
## **DO NOT ASK USER FOR FEEDBACK — THIS IS THE USER FEEDBACK.**
|
|
4
|
+
## **DO NOT ASK USER FOR NEXT STEPS — THESE ARE THE NEXT STEPS.**
|
|
5
|
+
|
|
6
|
+
Goal: `db_uri` decides the backend. A postgres `db_uri` in config → postgres
|
|
7
|
+
(compose service offered by init, FTS via tsvector/GIN, MCP read-only path
|
|
8
|
+
enforced per-dialect). Absent that → sqlite + local FS, exactly as today.
|
|
9
|
+
Motivation: central agent state shared across machines (coast-after-3 + this
|
|
10
|
+
box) — sqlite has no cross-host story; the task/delivery mailbox design is
|
|
11
|
+
already row-level-safe under postgres.
|
|
12
|
+
|
|
13
|
+
Prior sprint (image object store / RustFS) is COMPLETE — see git history
|
|
14
|
+
86c1c474..f114d966. This file replaces it.
|
|
15
|
+
|
|
16
|
+
## Items (unordered)
|
|
17
|
+
|
|
18
|
+
- [x] models.py: swap `sqlalchemy.dialects.sqlite.JSON` → generic
|
|
19
|
+
`sqlalchemy.JSON` (no JSON-operator queries anywhere, so generic is
|
|
20
|
+
correct; keep schema dialect-free).
|
|
21
|
+
- [x] db.py `_require_v5`: PRAGMA table_info is sqlite-only → use SQLAlchemy
|
|
22
|
+
inspector (portable).
|
|
23
|
+
- [x] pyproject: add `psycopg[binary]` dependency (sync driver; engine is
|
|
24
|
+
sync SQLAlchemy throughout). URI form `postgresql+psycopg://`.
|
|
25
|
+
- [x] FTS seam: new `memory/fts.py` owning ALL dialect-specific full-text
|
|
26
|
+
code — create_fts / insert_fts / search_fts. sqlite = FTS5+bm25
|
|
27
|
+
(unchanged behavior); postgres = messages_fts(rowid BIGINT PK, tsv
|
|
28
|
+
tsvector) + GIN index, to_tsvector('simple'), plainto_tsquery,
|
|
29
|
+
ts_rank NEGATED so the contract stays "lower = better, best first"
|
|
30
|
+
(mcp/memory/main.py:359 negates again for display — keep it working).
|
|
31
|
+
Wire into db.create_database, writes.add_message, reads.search_messages.
|
|
32
|
+
- [x] MCP dialect awareness: `get_ro_engine` in memory/db.py — sqlite keeps
|
|
33
|
+
`?mode=ro&uri=true`; postgres gets a connect listener
|
|
34
|
+
`SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY`. store.py
|
|
35
|
+
`_ro_engine` delegates; sqlite file-existence check stays.
|
|
36
|
+
- [x] defaults.py COMPOSE_YAML: postgres service (postgres:17-alpine,
|
|
37
|
+
POSTGRES_USER/PASSWORD/DB env refs, pg_isready healthcheck,
|
|
38
|
+
postgres_data volume) — same env-ref-credentials pattern as rustfs.
|
|
39
|
+
- [x] init_cmd.py: Step 2c PostgreSQL (mirror Step 2b rustfs: --yes default,
|
|
40
|
+
YES_INSTALL_POSTGRES, Confirm.ask; POSTGRES_PORT/USER/DB/PASSWORD into
|
|
41
|
+
env_vars, password random-default via secrets; db_uri becomes
|
|
42
|
+
postgresql+psycopg://${...}@localhost:${POSTGRES_PORT}/${POSTGRES_DB};
|
|
43
|
+
compose writer includes postgres; review/done panels reflect it).
|
|
44
|
+
- [x] config.py apply_config_overrides: resolve_env_vars on db_uri (load()
|
|
45
|
+
already resolves whole file at line 335).
|
|
46
|
+
- [x] CONFIG_YAML template: db_uri comment shows the postgres option.
|
|
47
|
+
- [x] Tests: tests/unit/test_init_postgres.py (mirror test_init_rustfs.py);
|
|
48
|
+
RO-engine dialect branch tests; fts seam tests (sqlite path = existing
|
|
49
|
+
suite; postgres path = live e2e tier).
|
|
50
|
+
- [x] Live e2e: scripts/e2e_postgres_live.py — ephemeral postgres:17-alpine
|
|
51
|
+
container; create_database; add_message→search_messages round trip;
|
|
52
|
+
JSON data round trip; claim_deliveries exactly-once with 2 concurrent
|
|
53
|
+
claimers; RO engine refuses writes; print E2E-POSTGRES-OK.
|
|
54
|
+
- [x] Docs: README persistence section (postgres option), memory/__init__.py
|
|
55
|
+
docstring ("except FTS5" is now false — both backends supported).
|
|
56
|
+
- [x] Full suite green at every phase boundary; commit per phase with
|
|
57
|
+
Session-Id trailer.
|
|
58
|
+
|
|
59
|
+
## Decisions (locked)
|
|
60
|
+
|
|
61
|
+
- Generic `sqlalchemy.JSON`, not JSONB variant: no JSON-operator queries
|
|
62
|
+
exist; keep the schema dialect-free.
|
|
63
|
+
- Postgres FTS mirrors the sqlite architecture (side table maintained from
|
|
64
|
+
Python in the same transaction) — NO trigger on messages.data, because
|
|
65
|
+
message_text() extraction logic lives in Python.
|
|
66
|
+
- 'simple' ts config for keyword parity with FTS5 (no stemming surprises).
|
|
67
|
+
- Score contract preserved: search_messages returns lower=better on both
|
|
68
|
+
backends (ts_rank negated).
|
|
69
|
+
- No sqlite→postgres history migration tool this sprint (fresh postgres DB;
|
|
70
|
+
migration is a future task if wanted).
|
|
71
|
+
- postgres compose service has NO custom network (default compose network is
|
|
72
|
+
fine; rustfs-network stays rustfs-only).
|
|
73
|
+
## Status: COMPLETE 2026-08-26 — all items done; evidence in PLAN.md + commits 58061203..HEAD.
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "crow-cli"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.38"
|
|
4
4
|
description = "Add your description here"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.14"
|
|
7
7
|
dependencies = [
|
|
8
8
|
"agent-client-protocol[http]>=0.12.0,<0.13.0",
|
|
9
|
+
"boto3>=1.40.0",
|
|
9
10
|
"hypercorn>=0.17",
|
|
10
11
|
"coolname>=2.2.0",
|
|
11
12
|
"directory-tree>=1.0.0",
|
|
@@ -16,6 +17,7 @@ dependencies = [
|
|
|
16
17
|
"markdownify>=0.14.0",
|
|
17
18
|
"openai>=2.21.0",
|
|
18
19
|
"opencv-python>=4.13.0.92",
|
|
20
|
+
"psycopg[binary]>=3.1",
|
|
19
21
|
"pydantic>=2.12.5",
|
|
20
22
|
"pyyaml>=6.0.3",
|
|
21
23
|
"readabilipy>=0.3.0",
|
|
@@ -43,6 +45,7 @@ packages = ["src/crow_cli"]
|
|
|
43
45
|
[dependency-groups]
|
|
44
46
|
dev = [
|
|
45
47
|
"ipython>=9.10.0",
|
|
48
|
+
"moto[s3,server]>=5.0.0",
|
|
46
49
|
"pytest>=9.0.2",
|
|
47
50
|
"pytest-asyncio>=0.23.0",
|
|
48
51
|
"pyinstaller>=6.0.0",
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Live e2e: image store against a REAL RustFS container.
|
|
3
|
+
|
|
4
|
+
Boots an ephemeral rustfs/rustfs container (SNSD), then drives the real
|
|
5
|
+
MemoryClient through the full seam:
|
|
6
|
+
1. S3 reachable -> resolve_image_store picks HybridReadStore(S3, FS)
|
|
7
|
+
2. save a message with an inline base64 image -> object lands in the bucket
|
|
8
|
+
3. load + hydrate -> identical base64 comes back
|
|
9
|
+
4. legacy FS image (pre-placed) still hydrates through the hybrid read
|
|
10
|
+
5. container killed -> next init falls back to FsImageStore
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
uv --project . run python scripts/e2e_image_store_live.py
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import base64
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
import tempfile
|
|
21
|
+
import time
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
import boto3
|
|
25
|
+
import httpx
|
|
26
|
+
|
|
27
|
+
from crow_cli.agent.memory import MemoryClient
|
|
28
|
+
from crow_cli.memory.image_store import FsImageStore, HybridReadStore
|
|
29
|
+
|
|
30
|
+
PORT = 19000
|
|
31
|
+
ENDPOINT = f"http://127.0.0.1:{PORT}"
|
|
32
|
+
CONTAINER = "crow-rustfs-e2e"
|
|
33
|
+
BUCKET = "crow-images"
|
|
34
|
+
ACCESS_KEY = "crowe2e"
|
|
35
|
+
SECRET_KEY = "crowe2e"
|
|
36
|
+
|
|
37
|
+
PNG_BYTES = bytes([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 1, 2, 3, 4])
|
|
38
|
+
PNG_B64 = base64.b64encode(PNG_BYTES).decode()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def docker(*args: str) -> str:
|
|
42
|
+
return subprocess.run(
|
|
43
|
+
["docker", *args], capture_output=True, text=True, check=True
|
|
44
|
+
).stdout.strip()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def start_rustfs() -> None:
|
|
48
|
+
subprocess.run(["docker", "rm", "-f", CONTAINER], capture_output=True)
|
|
49
|
+
docker(
|
|
50
|
+
"run", "-d", "--name", CONTAINER,
|
|
51
|
+
"-p", f"{PORT}:9000",
|
|
52
|
+
"-e", f"RUSTFS_ACCESS_KEY={ACCESS_KEY}",
|
|
53
|
+
"-e", f"RUSTFS_SECRET_KEY={SECRET_KEY}",
|
|
54
|
+
"-e", "RUSTFS_ADDRESS=0.0.0.0:9000",
|
|
55
|
+
"-e", "RUSTFS_UNSAFE_BYPASS_DISK_CHECK=true",
|
|
56
|
+
"rustfs/rustfs:latest", "/data",
|
|
57
|
+
)
|
|
58
|
+
for _ in range(60):
|
|
59
|
+
try:
|
|
60
|
+
r = httpx.get(f"{ENDPOINT}/health", timeout=1.0)
|
|
61
|
+
if r.status_code < 500:
|
|
62
|
+
print(f"[ok] rustfs healthy at {ENDPOINT}")
|
|
63
|
+
return
|
|
64
|
+
except httpx.HTTPError:
|
|
65
|
+
pass
|
|
66
|
+
time.sleep(1)
|
|
67
|
+
raise SystemExit("[FAIL] rustfs never became healthy")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def stop_rustfs() -> None:
|
|
71
|
+
subprocess.run(["docker", "rm", "-f", CONTAINER], capture_output=True)
|
|
72
|
+
print("[ok] container removed")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def write_config(config_dir: Path) -> None:
|
|
76
|
+
(config_dir / "config.yaml").write_text(
|
|
77
|
+
f"db_uri: sqlite:///{config_dir / 'crow.db'}\n"
|
|
78
|
+
"image_store:\n"
|
|
79
|
+
" s3:\n"
|
|
80
|
+
f" endpoint: {ENDPOINT}\n"
|
|
81
|
+
f" bucket: {BUCKET}\n"
|
|
82
|
+
f" access_key: {ACCESS_KEY}\n"
|
|
83
|
+
f" secret_key: {SECRET_KEY}\n"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def image_msg() -> dict:
|
|
88
|
+
return {
|
|
89
|
+
"role": "tool",
|
|
90
|
+
"tool_call_id": "e2e-c1",
|
|
91
|
+
"content": [
|
|
92
|
+
{"type": "text", "text": "live rustfs drill"},
|
|
93
|
+
{
|
|
94
|
+
"type": "image_url",
|
|
95
|
+
"image_url": {"url": f"data:image/png;base64,{PNG_B64}"},
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
async def run() -> None:
|
|
102
|
+
tmp = Path(tempfile.mkdtemp(prefix="crow-rustfs-e2e-"))
|
|
103
|
+
write_config(tmp)
|
|
104
|
+
|
|
105
|
+
# ---- 1-3: S3 up — store choice, write into bucket, hydrate back ----
|
|
106
|
+
async with MemoryClient(config_dir=tmp) as client:
|
|
107
|
+
assert isinstance(client.image_store, HybridReadStore), (
|
|
108
|
+
f"expected HybridReadStore, got {type(client.image_store).__name__}"
|
|
109
|
+
)
|
|
110
|
+
print("[ok] resolve_image_store picked S3 (HybridReadStore)")
|
|
111
|
+
|
|
112
|
+
agent_id = "e2e-rustfs-1-1"
|
|
113
|
+
await client.create_agent(
|
|
114
|
+
agent_id=agent_id, session_id="e2e-rustfs", agent_idx=1,
|
|
115
|
+
cwd="/tmp", system_prompt="sp", tool_definitions=[],
|
|
116
|
+
request_params={}, model_identifier="m",
|
|
117
|
+
)
|
|
118
|
+
await client.add_message(agent_id, image_msg())
|
|
119
|
+
|
|
120
|
+
# object actually landed in the bucket (checked via independent client)
|
|
121
|
+
s3 = boto3.client(
|
|
122
|
+
"s3", endpoint_url=ENDPOINT,
|
|
123
|
+
aws_access_key_id=ACCESS_KEY, aws_secret_access_key=SECRET_KEY,
|
|
124
|
+
region_name="us-east-1",
|
|
125
|
+
)
|
|
126
|
+
keys = [o["Key"] for o in s3.list_objects_v2(Bucket=BUCKET).get("Contents", [])]
|
|
127
|
+
assert len(keys) == 1 and keys[0].endswith(".png"), f"bucket keys: {keys}"
|
|
128
|
+
print(f"[ok] object in bucket: {keys[0]}")
|
|
129
|
+
|
|
130
|
+
# stored row carries an image_ref, NOT base64
|
|
131
|
+
_, stored = await client.load(agent_id)
|
|
132
|
+
ref = stored[0]["content"][1]
|
|
133
|
+
assert ref["type"] == "image_ref" and ref["path"] == keys[0]
|
|
134
|
+
print("[ok] sqlite row carries image_ref only")
|
|
135
|
+
|
|
136
|
+
# hydrated load round-trips the exact bytes
|
|
137
|
+
_, hydrated = await client.load(agent_id, hydrate=True)
|
|
138
|
+
url = hydrated[0]["content"][1]["image_url"]["url"]
|
|
139
|
+
assert url == f"data:image/png;base64,{PNG_B64}"
|
|
140
|
+
print("[ok] hydrate round-trip: identical base64")
|
|
141
|
+
|
|
142
|
+
# ---- 4: legacy FS image still hydrates through the hybrid read ----
|
|
143
|
+
images_dir = client.images_dir
|
|
144
|
+
fs = FsImageStore(images_dir)
|
|
145
|
+
fs.put("legacydeadbeef.png", PNG_BYTES)
|
|
146
|
+
await client.add_message(
|
|
147
|
+
agent_id,
|
|
148
|
+
{
|
|
149
|
+
"role": "tool",
|
|
150
|
+
"tool_call_id": "e2e-c2",
|
|
151
|
+
"content": [{"type": "image_ref", "path": "legacydeadbeef.png", "mime": "image/png"}],
|
|
152
|
+
},
|
|
153
|
+
)
|
|
154
|
+
_, hydrated2 = await client.load(agent_id, hydrate=True)
|
|
155
|
+
url2 = hydrated2[1]["content"][0]["image_url"]["url"]
|
|
156
|
+
assert url2 == f"data:image/png;base64,{PNG_B64}"
|
|
157
|
+
print("[ok] legacy FS image hydrates via hybrid read-fallback")
|
|
158
|
+
|
|
159
|
+
# ---- 5: kill the container -> next init falls back to FS ----
|
|
160
|
+
stop_rustfs()
|
|
161
|
+
async with MemoryClient(config_dir=tmp) as client:
|
|
162
|
+
assert isinstance(client.image_store, FsImageStore), (
|
|
163
|
+
f"expected FsImageStore fallback, got {type(client.image_store).__name__}"
|
|
164
|
+
)
|
|
165
|
+
print("[ok] endpoint down -> FsImageStore fallback")
|
|
166
|
+
|
|
167
|
+
print("\nE2E-IMAGE-STORE-OK")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
if __name__ == "__main__":
|
|
171
|
+
if subprocess.run(["docker", "pull", "rustfs/rustfs:latest"], capture_output=True).returncode != 0:
|
|
172
|
+
print("[..] pull failed — trying cached image")
|
|
173
|
+
start_rustfs()
|
|
174
|
+
try:
|
|
175
|
+
asyncio.run(run())
|
|
176
|
+
finally:
|
|
177
|
+
stop_rustfs()
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Live e2e: memory layer against a REAL PostgreSQL container.
|
|
3
|
+
|
|
4
|
+
Boots an ephemeral postgres:17-alpine container, then drives the real
|
|
5
|
+
memory package through the postgres path of every dialect seam:
|
|
6
|
+
1. create_database -> tables + messages_fts tsvector table + GIN index
|
|
7
|
+
2. add_message/search_messages -> tsvector FTS round trip, lower=better
|
|
8
|
+
3. JSON column round trip (Message.data) byte-identical
|
|
9
|
+
4. claim_deliveries with 2 concurrent claimers -> exactly one wins
|
|
10
|
+
(row-level atomicity on postgres — the multi-machine mailbox story)
|
|
11
|
+
5. get_ro_engine -> SELECT ok, INSERT refused by the server
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
uv --project . run python scripts/e2e_postgres_live.py
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import subprocess
|
|
18
|
+
import threading
|
|
19
|
+
import time
|
|
20
|
+
|
|
21
|
+
from sqlalchemy import text
|
|
22
|
+
from sqlalchemy.exc import SQLAlchemyError
|
|
23
|
+
|
|
24
|
+
from crow_cli.memory import (
|
|
25
|
+
add_message,
|
|
26
|
+
claim_deliveries,
|
|
27
|
+
create_agent,
|
|
28
|
+
create_database,
|
|
29
|
+
finish_task,
|
|
30
|
+
get_engine,
|
|
31
|
+
get_ro_engine,
|
|
32
|
+
launch_task,
|
|
33
|
+
load_messages,
|
|
34
|
+
search_messages,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
PORT = 15432
|
|
38
|
+
CONTAINER = "crow-pg-e2e"
|
|
39
|
+
USER = PASSWORD = DB = "crowe2e"
|
|
40
|
+
URI = f"postgresql+psycopg://{USER}:{PASSWORD}@127.0.0.1:{PORT}/{DB}"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def docker(*args: str) -> str:
|
|
44
|
+
return subprocess.run(
|
|
45
|
+
["docker", *args], capture_output=True, text=True, check=True
|
|
46
|
+
).stdout.strip()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def start_postgres() -> None:
|
|
50
|
+
subprocess.run(["docker", "rm", "-f", CONTAINER], capture_output=True)
|
|
51
|
+
docker(
|
|
52
|
+
"run", "-d", "--name", CONTAINER,
|
|
53
|
+
"-p", f"{PORT}:5432",
|
|
54
|
+
"-e", f"POSTGRES_USER={USER}",
|
|
55
|
+
"-e", f"POSTGRES_PASSWORD={PASSWORD}",
|
|
56
|
+
"-e", f"POSTGRES_DB={DB}",
|
|
57
|
+
"postgres:17-alpine",
|
|
58
|
+
)
|
|
59
|
+
# Probe with a REAL connection: pg_isready can succeed against the
|
|
60
|
+
# throwaway server the image runs during initdb, before the restart.
|
|
61
|
+
for _ in range(60):
|
|
62
|
+
try:
|
|
63
|
+
probe = get_engine(URI)
|
|
64
|
+
with probe.connect() as conn:
|
|
65
|
+
conn.execute(text("SELECT 1"))
|
|
66
|
+
probe.dispose()
|
|
67
|
+
print(f"[ok] postgres accepting connections at 127.0.0.1:{PORT}")
|
|
68
|
+
return
|
|
69
|
+
except SQLAlchemyError:
|
|
70
|
+
time.sleep(1)
|
|
71
|
+
raise SystemExit("[FAIL] postgres never became ready")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def stop_postgres() -> None:
|
|
75
|
+
subprocess.run(["docker", "rm", "-f", CONTAINER], capture_output=True)
|
|
76
|
+
print("[ok] container removed")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def run() -> None:
|
|
80
|
+
# ---- 1: schema + FTS objects on postgres ----
|
|
81
|
+
create_database(URI)
|
|
82
|
+
engine = get_engine(URI)
|
|
83
|
+
with engine.connect() as conn:
|
|
84
|
+
tables = {
|
|
85
|
+
r[0]
|
|
86
|
+
for r in conn.execute(
|
|
87
|
+
text(
|
|
88
|
+
"SELECT table_name FROM information_schema.tables "
|
|
89
|
+
"WHERE table_schema = 'public'"
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
indexes = {
|
|
94
|
+
r[0]
|
|
95
|
+
for r in conn.execute(
|
|
96
|
+
text("SELECT indexname FROM pg_indexes WHERE schemaname = 'public'")
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
for t in ("agents", "messages", "tasks", "task_deliveries", "prompts", "messages_fts"):
|
|
100
|
+
assert t in tables, f"missing table {t}: {tables}"
|
|
101
|
+
assert "idx_messages_fts_tsv" in indexes, f"missing GIN index: {indexes}"
|
|
102
|
+
print("[ok] create_database: tables + messages_fts + GIN index")
|
|
103
|
+
|
|
104
|
+
# ---- 2: FTS write/search round trip, lower=better contract ----
|
|
105
|
+
create_agent(
|
|
106
|
+
engine,
|
|
107
|
+
agent_id="e2e-pg-1-1",
|
|
108
|
+
session_id="e2e-pg",
|
|
109
|
+
agent_idx=1,
|
|
110
|
+
cwd="/tmp",
|
|
111
|
+
system_prompt="sp",
|
|
112
|
+
tool_definitions=[],
|
|
113
|
+
request_params={},
|
|
114
|
+
model_identifier="m",
|
|
115
|
+
)
|
|
116
|
+
add_message(
|
|
117
|
+
engine, "e2e-pg-1-1",
|
|
118
|
+
{"role": "user", "content": "the quick brown fox jumps over the lazy dog"},
|
|
119
|
+
)
|
|
120
|
+
add_message(
|
|
121
|
+
engine, "e2e-pg-1-1",
|
|
122
|
+
{"role": "user", "content": "postgres full text search drill"},
|
|
123
|
+
)
|
|
124
|
+
hits = search_messages(engine, "postgres search", limit=10)
|
|
125
|
+
assert len(hits) == 1, f"expected 1 hit, got {len(hits)}"
|
|
126
|
+
assert hits[0]["session_id"] == "e2e-pg"
|
|
127
|
+
assert hits[0]["score"] < 0, f"ts_rank must be negated (lower=better): {hits[0]['score']}"
|
|
128
|
+
hits2 = search_messages(engine, "fox dog", limit=10)
|
|
129
|
+
assert len(hits2) == 1 and "fox" in hits2[0]["data"]["content"]
|
|
130
|
+
assert search_messages(engine, " ", limit=10) == []
|
|
131
|
+
print("[ok] search_messages: tsvector round trip, lower=better, empty-safe")
|
|
132
|
+
|
|
133
|
+
# ---- 3: JSON column round trip ----
|
|
134
|
+
rich_msg = {
|
|
135
|
+
"role": "assistant",
|
|
136
|
+
"content": [
|
|
137
|
+
{"type": "text", "text": "json round trip"},
|
|
138
|
+
{"type": "tool_call", "name": "terminal", "arguments": {"command": "ls -la"}},
|
|
139
|
+
],
|
|
140
|
+
"reasoning_content": "nested dicts survive",
|
|
141
|
+
}
|
|
142
|
+
add_message(engine, "e2e-pg-1-1", rich_msg)
|
|
143
|
+
rows = load_messages(engine, "e2e-pg-1-1")
|
|
144
|
+
stored = [r for r in rows if r.get("reasoning_content")]
|
|
145
|
+
assert len(stored) == 1 and stored[0] == rich_msg, f"round trip mismatch: {stored}"
|
|
146
|
+
print("[ok] Message.data JSON round trip: identical dict")
|
|
147
|
+
|
|
148
|
+
# ---- 4: mailbox exactly-once under concurrent claimers ----
|
|
149
|
+
launch_task(
|
|
150
|
+
engine,
|
|
151
|
+
task_id="e2e-t1",
|
|
152
|
+
owner_session="e2e-pg",
|
|
153
|
+
tool_call_id="tc1",
|
|
154
|
+
prompt="drill",
|
|
155
|
+
)
|
|
156
|
+
assert finish_task(engine, "e2e-t1", result="done", content="completion payload")
|
|
157
|
+
|
|
158
|
+
barrier = threading.Barrier(2)
|
|
159
|
+
results: list[list] = [[], []]
|
|
160
|
+
|
|
161
|
+
def claim(i: int) -> None:
|
|
162
|
+
barrier.wait()
|
|
163
|
+
results[i] = claim_deliveries(engine, "e2e-pg")
|
|
164
|
+
|
|
165
|
+
threads = [threading.Thread(target=claim, args=(i,)) for i in range(2)]
|
|
166
|
+
for t in threads:
|
|
167
|
+
t.start()
|
|
168
|
+
for t in threads:
|
|
169
|
+
t.join()
|
|
170
|
+
total = [d for r in results for d in r]
|
|
171
|
+
assert len(total) == 1, f"delivery claimed {len(total)} times: {results}"
|
|
172
|
+
assert total[0]["content"] == "completion payload"
|
|
173
|
+
print("[ok] claim_deliveries: 2 concurrent claimers, exactly one won")
|
|
174
|
+
|
|
175
|
+
# ---- 5: read-only engine enforced by the server ----
|
|
176
|
+
ro = get_ro_engine(URI)
|
|
177
|
+
with ro.connect() as conn:
|
|
178
|
+
assert conn.execute(text("SELECT COUNT(*) FROM messages")).scalar() == 3
|
|
179
|
+
try:
|
|
180
|
+
with ro.begin() as conn:
|
|
181
|
+
conn.execute(
|
|
182
|
+
text(
|
|
183
|
+
"INSERT INTO prompts(id, name, template, created_at) "
|
|
184
|
+
"VALUES ('x', 'x', 'x', 'x')"
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
raise AssertionError("read-only engine accepted a write")
|
|
188
|
+
except SQLAlchemyError as e:
|
|
189
|
+
assert "read-only" in str(e).lower(), f"unexpected error: {e}"
|
|
190
|
+
print("[ok] get_ro_engine: SELECT ok, INSERT refused by postgres")
|
|
191
|
+
|
|
192
|
+
engine.dispose()
|
|
193
|
+
ro.dispose()
|
|
194
|
+
print("\nE2E-POSTGRES-OK")
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
if __name__ == "__main__":
|
|
198
|
+
if subprocess.run(["docker", "pull", "postgres:17-alpine"], capture_output=True).returncode != 0:
|
|
199
|
+
print("[..] pull failed — trying cached image")
|
|
200
|
+
start_postgres()
|
|
201
|
+
try:
|
|
202
|
+
run()
|
|
203
|
+
finally:
|
|
204
|
+
stop_postgres()
|