wagent-framework 2.0.0a2__tar.gz → 2.0.0a3__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.
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/PKG-INFO +5 -5
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/README.md +4 -4
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/setup.py +1 -1
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_cli.py +1 -1
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_local_runtime.py +21 -1
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_sessions.py +11 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_tui.py +181 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/__init__.py +9 -7
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/local_runtime.py +10 -1
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/sessions.py +65 -18
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/tools/mcp_client.py +1 -1
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/tui.py +235 -6
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/wagent_framework.egg-info/PKG-INFO +5 -5
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/LICENSE +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/setup.cfg +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_agent_templates.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_aop.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_component_chain.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_compositions.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_config.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_container.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_deployment.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_distributed_lock.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_doctor.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_evaluation.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_event_bus.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_http_provider_templates.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_kernel_events.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_kernel_loading.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_kernel_plugins.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_kernel_registry.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_legacy_compat.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_lifecycle.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_mcp_client.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_model_invocation.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_model_probing.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_model_routing.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_model_testing.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_model_types.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_observability.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_openai_compatible_provider.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_package_integrity.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_pricing.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_react_agent.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_sandbox.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_sandbox_runtime.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_scanner.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_timeout.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_tool_adapters.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_tool_loading.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_tool_runtime.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_workflow_adapters.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/tests/test_workflow_engine.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/__main__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/agents/__init__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/agents/persistence.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/agents/react.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/agents/templates.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/agents/types.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/aop/__init__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/aop/aspects.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/aop/joinpoint.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/aop/pointcut.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/aop/proxy_factory.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/cli.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/compat.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/compositions.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/config/__init__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/config/dynamic_config.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/container/__init__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/container/bean_factory.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/container/reflection_cache.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/core/__init__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/core/agent.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/core/decorators.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/core/doctor.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/core/event_bus.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/deployment/__init__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/deployment/fastapi_depends.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/distributed/__init__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/distributed/lock.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/distributed/lock_pool.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/evaluation.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/exceptions/__init__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/exceptions/framework_errors.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/kernel/__init__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/kernel/events.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/kernel/exceptions.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/kernel/loading.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/kernel/plugins.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/kernel/registry.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/kernel/scope.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/lifecycle/__init__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/lifecycle/graceful_shutdown.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/lifecycle/manager.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/lifecycle/order.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/migration/__init__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/migration/migrate_previous.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/models/__init__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/models/errors.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/models/http_provider.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/models/invocation.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/models/native_mappings.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/models/openai_compatible.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/models/pricing.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/models/probing.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/models/provider.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/models/routing.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/models/templates.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/models/types.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/observability/__init__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/observability/health.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/observability/logging.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/observability/metrics.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/observability/tracing.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/resilience/__init__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/resilience/bulkhead.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/resilience/timeout.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/sandbox/__init__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/sandbox/docker.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/sandbox/process.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/sandbox/registry.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/sandbox/types.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/sandbox/unsafe_local.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/scanner/__init__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/scanner/cache.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/scanner/parallel_scanner.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/security/__init__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/security/mcp_auth.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/skills/__init__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/skills/sandbox/__init__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/skills/sandbox/nsjail_sandbox.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/skills/sandbox/wasm_sandbox.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/skills/signature.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/skills/skill.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/testing/__init__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/testing/mock_utils.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/testing/model_provider.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/tools/__init__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/tools/command_adapter.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/tools/execution.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/tools/http_adapter.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/tools/langchain_adapter.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/tools/loading.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/tools/mcp_adapter.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/tools/policies.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/tools/python.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/tools/registry.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/tools/sandbox_adapter.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/tools/types.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/workflows/__init__.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/workflows/adapters.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/workflows/engine.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/workflows/persistence.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/workflows/registry.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/w_agent/workflows/types.py +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/wagent_framework.egg-info/SOURCES.txt +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/wagent_framework.egg-info/dependency_links.txt +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/wagent_framework.egg-info/entry_points.txt +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/wagent_framework.egg-info/requires.txt +0 -0
- {wagent_framework-2.0.0a2 → wagent_framework-2.0.0a3}/wagent_framework.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wagent-framework
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.0a3
|
|
4
4
|
Summary: Open, composable Python framework for building agents
|
|
5
5
|
Home-page: https://github.com/LuckyStar2456/W-Agent-FrameWork
|
|
6
6
|
Author: LuckyStar2456
|
|
@@ -73,7 +73,7 @@ Dynamic: summary
|
|
|
73
73
|
|
|
74
74
|
W-Agent 是一个面向本地开发者的 Python 开源 Agent 开发框架。它的目标不是提供托管平台或固定 Harness,而是提供稳定、可扩展的协议与可自由装配的模块,让开发者能够替换模型、路由、Agent Loop、Workflow、工具、状态、沙箱和界面实现。
|
|
75
75
|
|
|
76
|
-
当前稳定发布版本是 `1.5.2`,最新 Alpha 为 `2.0.0a2
|
|
76
|
+
当前稳定发布版本是 `1.5.2`,最新 Alpha 为 `2.0.0a2`,仓库主线为 `2.0.0a3` 开发版。1.x 工程底座继续保留,微内核、模型、工具、单 Agent 与本地 Workflow 基础已经实现,其余下一代能力按路线图分阶段交付。文档使用明确状态标记,避免把规划能力描述为现有能力。
|
|
77
77
|
|
|
78
78
|
## 状态标记
|
|
79
79
|
|
|
@@ -109,7 +109,7 @@ W-Agent 遵循以下原则:
|
|
|
109
109
|
- Skill 加载、签名校验、MCP JWT 认证、Redis 分布式锁。
|
|
110
110
|
- LangChain 工具适配、FastAPI 示例集成和测试辅助设施。
|
|
111
111
|
|
|
112
|
-
以下 Phase 1 能力在当前 `2.0.
|
|
112
|
+
以下 Phase 1 能力在当前 `2.0.0a3` 源码中为 `Implemented`:
|
|
113
113
|
|
|
114
114
|
- `PluginSpec`、装饰器、YAML 引用与 Python entry point 发现。
|
|
115
115
|
- 统一、版本感知、分作用域的能力注册表与不可变快照。
|
|
@@ -175,7 +175,7 @@ pip install "wagent-framework[wasm]"
|
|
|
175
175
|
pip install "wagent-framework[tui]"
|
|
176
176
|
```
|
|
177
177
|
|
|
178
|
-
PyPI 1.x 支持 Python 3.9+;`2.0.0a2`
|
|
178
|
+
PyPI 1.x 支持 Python 3.9+;`2.0.0a2`、当前 `2.0.0a3` 源码与后续 2.x Alpha 要求 Python 3.11+。
|
|
179
179
|
|
|
180
180
|
## 1.x 最小示例
|
|
181
181
|
|
|
@@ -221,7 +221,7 @@ wagent run "hello" --confirm-model-call --json
|
|
|
221
221
|
wagent evaluate cases.json --confirm-model-call --report report.json
|
|
222
222
|
```
|
|
223
223
|
|
|
224
|
-
CLI 和 TUI 只调用公开 Python API。当前 CLI/TUI 基础标记为 `Experimental`:已覆盖初始化、模板列表、安全/主动端点探测、装配管理/离线预览、本地 Session 生命周期、需逐次确认的配置化 Agent 运行与 Token 汇总、Agent Checkpoint
|
|
224
|
+
CLI 和 TUI 只调用公开 Python API。当前 CLI/TUI 基础标记为 `Experimental`:已覆盖初始化、模板列表、安全/主动端点探测、装配管理/离线预览、本地 Session 生命周期、需逐次确认的配置化 Agent 运行与 Token 汇总、Agent Checkpoint 脱敏列表、Catalog 工具选择、独立代码加载确认、权限授予、精确 Call ID 审批恢复、脱敏实时 RunEvent,以及 CLI/TUI 隐私安全本地评测;通用插件操作和 Workflow Checkpoint 汇总仍为 `Planned`。
|
|
225
225
|
|
|
226
226
|
## 工程装配分享
|
|
227
227
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
W-Agent 是一个面向本地开发者的 Python 开源 Agent 开发框架。它的目标不是提供托管平台或固定 Harness,而是提供稳定、可扩展的协议与可自由装配的模块,让开发者能够替换模型、路由、Agent Loop、Workflow、工具、状态、沙箱和界面实现。
|
|
6
6
|
|
|
7
|
-
当前稳定发布版本是 `1.5.2`,最新 Alpha 为 `2.0.0a2
|
|
7
|
+
当前稳定发布版本是 `1.5.2`,最新 Alpha 为 `2.0.0a2`,仓库主线为 `2.0.0a3` 开发版。1.x 工程底座继续保留,微内核、模型、工具、单 Agent 与本地 Workflow 基础已经实现,其余下一代能力按路线图分阶段交付。文档使用明确状态标记,避免把规划能力描述为现有能力。
|
|
8
8
|
|
|
9
9
|
## 状态标记
|
|
10
10
|
|
|
@@ -40,7 +40,7 @@ W-Agent 遵循以下原则:
|
|
|
40
40
|
- Skill 加载、签名校验、MCP JWT 认证、Redis 分布式锁。
|
|
41
41
|
- LangChain 工具适配、FastAPI 示例集成和测试辅助设施。
|
|
42
42
|
|
|
43
|
-
以下 Phase 1 能力在当前 `2.0.
|
|
43
|
+
以下 Phase 1 能力在当前 `2.0.0a3` 源码中为 `Implemented`:
|
|
44
44
|
|
|
45
45
|
- `PluginSpec`、装饰器、YAML 引用与 Python entry point 发现。
|
|
46
46
|
- 统一、版本感知、分作用域的能力注册表与不可变快照。
|
|
@@ -106,7 +106,7 @@ pip install "wagent-framework[wasm]"
|
|
|
106
106
|
pip install "wagent-framework[tui]"
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
-
PyPI 1.x 支持 Python 3.9+;`2.0.0a2`
|
|
109
|
+
PyPI 1.x 支持 Python 3.9+;`2.0.0a2`、当前 `2.0.0a3` 源码与后续 2.x Alpha 要求 Python 3.11+。
|
|
110
110
|
|
|
111
111
|
## 1.x 最小示例
|
|
112
112
|
|
|
@@ -152,7 +152,7 @@ wagent run "hello" --confirm-model-call --json
|
|
|
152
152
|
wagent evaluate cases.json --confirm-model-call --report report.json
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
-
CLI 和 TUI 只调用公开 Python API。当前 CLI/TUI 基础标记为 `Experimental`:已覆盖初始化、模板列表、安全/主动端点探测、装配管理/离线预览、本地 Session 生命周期、需逐次确认的配置化 Agent 运行与 Token 汇总、Agent Checkpoint
|
|
155
|
+
CLI 和 TUI 只调用公开 Python API。当前 CLI/TUI 基础标记为 `Experimental`:已覆盖初始化、模板列表、安全/主动端点探测、装配管理/离线预览、本地 Session 生命周期、需逐次确认的配置化 Agent 运行与 Token 汇总、Agent Checkpoint 脱敏列表、Catalog 工具选择、独立代码加载确认、权限授予、精确 Call ID 审批恢复、脱敏实时 RunEvent,以及 CLI/TUI 隐私安全本地评测;通用插件操作和 Workflow Checkpoint 汇总仍为 `Planned`。
|
|
156
156
|
|
|
157
157
|
## 工程装配分享
|
|
158
158
|
|
|
@@ -8,7 +8,7 @@ with open(os.path.join(here, "README.md"), "r", encoding="utf-8") as f:
|
|
|
8
8
|
|
|
9
9
|
setup(
|
|
10
10
|
name="wagent-framework",
|
|
11
|
-
version="2.0.
|
|
11
|
+
version="2.0.0a3",
|
|
12
12
|
description="Open, composable Python framework for building agents",
|
|
13
13
|
long_description=long_description,
|
|
14
14
|
long_description_content_type="text/markdown",
|
|
@@ -25,7 +25,7 @@ def test_cli_version_and_profile_json_are_machine_readable():
|
|
|
25
25
|
profiles = runner.invoke(app, ["profile", "list", "--json"])
|
|
26
26
|
|
|
27
27
|
assert version.exit_code == 0
|
|
28
|
-
assert "2.0.
|
|
28
|
+
assert "2.0.0a3" in version.stdout
|
|
29
29
|
assert profiles.exit_code == 0
|
|
30
30
|
assert [item["key"] for item in json.loads(profiles.stdout)] == [
|
|
31
31
|
"customer-support",
|
|
@@ -97,6 +97,11 @@ def _templates(captured):
|
|
|
97
97
|
@pytest.mark.asyncio
|
|
98
98
|
async def test_local_runtime_assembles_full_text_run_and_persists_session(tmp_path):
|
|
99
99
|
captured = {}
|
|
100
|
+
events = []
|
|
101
|
+
|
|
102
|
+
async def record_event(event):
|
|
103
|
+
events.append(event)
|
|
104
|
+
|
|
100
105
|
runtime = assemble_local_runtime(
|
|
101
106
|
_config(),
|
|
102
107
|
tmp_path / ".wagent",
|
|
@@ -104,9 +109,20 @@ async def test_local_runtime_assembles_full_text_run_and_persists_session(tmp_pa
|
|
|
104
109
|
environ={"TEST_MODEL_KEY": "not-persisted"},
|
|
105
110
|
)
|
|
106
111
|
|
|
107
|
-
run = await runtime.run(
|
|
112
|
+
run = await runtime.run(
|
|
113
|
+
"hi",
|
|
114
|
+
session_title="Configured run",
|
|
115
|
+
event_callback=record_event,
|
|
116
|
+
)
|
|
108
117
|
|
|
109
118
|
assert run.result.output == "hello"
|
|
119
|
+
assert [event.type.value for event in events] == [
|
|
120
|
+
"run-started",
|
|
121
|
+
"model-started",
|
|
122
|
+
"model-completed",
|
|
123
|
+
"token-usage",
|
|
124
|
+
"run-completed",
|
|
125
|
+
]
|
|
110
126
|
assert run.result.usage == TokenUsage(3, 2)
|
|
111
127
|
assert run.result.usage_complete is True
|
|
112
128
|
assert len(run.result.attempts) == 1
|
|
@@ -393,17 +409,21 @@ async def test_local_runtime_selects_catalog_tools_and_resumes_approval(tmp_path
|
|
|
393
409
|
assert pending.result.pending_tool_call.id == "save-1"
|
|
394
410
|
assert writes == []
|
|
395
411
|
|
|
412
|
+
resumed_events = []
|
|
396
413
|
completed = await runtime.resume(
|
|
397
414
|
pending.session.session_id,
|
|
398
415
|
pending.result.run_id,
|
|
399
416
|
permissions=frozenset({"notes.write"}),
|
|
400
417
|
approved_call_ids=frozenset({"save-1"}),
|
|
418
|
+
event_callback=resumed_events.append,
|
|
401
419
|
)
|
|
402
420
|
|
|
403
421
|
assert completed.result.output == "saved"
|
|
404
422
|
assert completed.result.stop_reason.value == "completed"
|
|
405
423
|
assert writes == ["value"]
|
|
406
424
|
assert completed.result.usage == TokenUsage(7, 2)
|
|
425
|
+
assert resumed_events[0].type.value == "run-resumed"
|
|
426
|
+
assert resumed_events[-1].type.value == "run-completed"
|
|
407
427
|
|
|
408
428
|
|
|
409
429
|
def test_local_runtime_tool_selection_requires_budget_and_authorized_catalog(tmp_path):
|
|
@@ -132,6 +132,17 @@ async def test_session_resume_updates_existing_run_without_duplicate_input(tmp_p
|
|
|
132
132
|
run_id="run-approval",
|
|
133
133
|
)
|
|
134
134
|
|
|
135
|
+
with pytest.raises(SessionError, match="streamed approval resume"):
|
|
136
|
+
await manager.resume_agent(
|
|
137
|
+
loop,
|
|
138
|
+
"approval-1",
|
|
139
|
+
"run-approval",
|
|
140
|
+
tool_context=ToolExecutionContext(
|
|
141
|
+
approved_call_ids=frozenset({"call-1"})
|
|
142
|
+
),
|
|
143
|
+
event_callback=lambda event: None,
|
|
144
|
+
)
|
|
145
|
+
|
|
135
146
|
resumed = await manager.resume_agent(
|
|
136
147
|
loop,
|
|
137
148
|
"approval-1",
|
|
@@ -12,6 +12,8 @@ from w_agent import (
|
|
|
12
12
|
ModelDescriptor,
|
|
13
13
|
ModelMessage,
|
|
14
14
|
ModelCost,
|
|
15
|
+
RunEvent,
|
|
16
|
+
RunEventType,
|
|
15
17
|
RunResult,
|
|
16
18
|
StopReason,
|
|
17
19
|
TokenUsage,
|
|
@@ -109,6 +111,185 @@ async def test_tui_run_requires_explicit_confirmation_before_config_or_network(
|
|
|
109
111
|
assert "config" in str(app.query_one("#run-result").content).lower()
|
|
110
112
|
|
|
111
113
|
|
|
114
|
+
@pytest.mark.asyncio
|
|
115
|
+
async def test_tui_tool_code_requires_separate_import_confirmation(
|
|
116
|
+
tmp_path,
|
|
117
|
+
monkeypatch,
|
|
118
|
+
):
|
|
119
|
+
loaded = []
|
|
120
|
+
monkeypatch.setattr(
|
|
121
|
+
tui_module,
|
|
122
|
+
"load_tool_entries",
|
|
123
|
+
lambda entries: loaded.append(entries) or {},
|
|
124
|
+
)
|
|
125
|
+
app = WAgentTui(tmp_path)
|
|
126
|
+
|
|
127
|
+
async with app.run_test(size=(140, 65)) as pilot:
|
|
128
|
+
app.query_one(TabbedContent).active = "run"
|
|
129
|
+
await pilot.pause()
|
|
130
|
+
app.query_one("#run-prompt").value = "hello"
|
|
131
|
+
app.query_one("#run-tool-entries").value = "my_tools:bindings"
|
|
132
|
+
app.query_one("#run-confirm").value = "RUN"
|
|
133
|
+
await pilot.click("#run-button")
|
|
134
|
+
await pilot.pause()
|
|
135
|
+
|
|
136
|
+
assert loaded == []
|
|
137
|
+
assert "type LOAD TOOLS" in str(app.query_one("#run-result").content)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@pytest.mark.asyncio
|
|
141
|
+
async def test_tui_projects_live_events_without_prompt_or_arguments(
|
|
142
|
+
tmp_path,
|
|
143
|
+
monkeypatch,
|
|
144
|
+
):
|
|
145
|
+
class Runtime:
|
|
146
|
+
closed = False
|
|
147
|
+
|
|
148
|
+
async def __aenter__(self):
|
|
149
|
+
return self
|
|
150
|
+
|
|
151
|
+
async def __aexit__(self, *exc_info):
|
|
152
|
+
self.closed = True
|
|
153
|
+
|
|
154
|
+
async def run(self, prompt, **kwargs):
|
|
155
|
+
assert prompt == "SECRET_PROMPT"
|
|
156
|
+
assert kwargs["permissions"] == frozenset({"notes.write"})
|
|
157
|
+
await kwargs["event_callback"](
|
|
158
|
+
RunEvent(
|
|
159
|
+
1,
|
|
160
|
+
RunEventType.TOOL_REQUESTED,
|
|
161
|
+
"run-live",
|
|
162
|
+
{
|
|
163
|
+
"step": 1,
|
|
164
|
+
"call_id": "call-1",
|
|
165
|
+
"name": "save_note",
|
|
166
|
+
"arguments": {"text": "SECRET_ARGUMENT"},
|
|
167
|
+
"text": "SECRET_OUTPUT",
|
|
168
|
+
},
|
|
169
|
+
session_id="session-live",
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
return SimpleNamespace(
|
|
173
|
+
session=SimpleNamespace(session_id="session-live"),
|
|
174
|
+
result=RunResult(
|
|
175
|
+
"run-live",
|
|
176
|
+
StopReason.COMPLETED,
|
|
177
|
+
"done",
|
|
178
|
+
(ModelMessage.text(MessageRole.ASSISTANT, "done"),),
|
|
179
|
+
(),
|
|
180
|
+
steps=1,
|
|
181
|
+
tool_calls=1,
|
|
182
|
+
),
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
runtime = Runtime()
|
|
186
|
+
loaded = []
|
|
187
|
+
monkeypatch.setattr(tui_module, "load_local_runtime_config", lambda path: object())
|
|
188
|
+
monkeypatch.setattr(
|
|
189
|
+
tui_module,
|
|
190
|
+
"load_tool_entries",
|
|
191
|
+
lambda entries: loaded.append(entries) or {"save_note": object()},
|
|
192
|
+
)
|
|
193
|
+
monkeypatch.setattr(
|
|
194
|
+
tui_module,
|
|
195
|
+
"assemble_local_runtime",
|
|
196
|
+
lambda config, state_root, *, tool_bindings: runtime,
|
|
197
|
+
)
|
|
198
|
+
app = WAgentTui(tmp_path)
|
|
199
|
+
|
|
200
|
+
async with app.run_test(size=(150, 70)) as pilot:
|
|
201
|
+
app.query_one(TabbedContent).active = "run"
|
|
202
|
+
await pilot.pause()
|
|
203
|
+
app.query_one("#run-prompt").value = "SECRET_PROMPT"
|
|
204
|
+
app.query_one("#run-tool-entries").value = "my_tools:bindings"
|
|
205
|
+
app.query_one("#run-permissions").value = "notes.write"
|
|
206
|
+
app.query_one("#run-tool-confirm").value = "LOAD TOOLS"
|
|
207
|
+
app.query_one("#run-confirm").value = "RUN"
|
|
208
|
+
await pilot.click("#run-button")
|
|
209
|
+
await pilot.pause()
|
|
210
|
+
|
|
211
|
+
rendered = str(app.query_one("#run-events").content)
|
|
212
|
+
assert "tool-requested" in rendered
|
|
213
|
+
assert "call_id=call-1" in rendered
|
|
214
|
+
assert "SECRET_PROMPT" not in rendered
|
|
215
|
+
assert "SECRET_ARGUMENT" not in rendered
|
|
216
|
+
assert "SECRET_OUTPUT" not in rendered
|
|
217
|
+
assert loaded == [("my_tools:bindings",)]
|
|
218
|
+
assert runtime.closed is True
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@pytest.mark.asyncio
|
|
222
|
+
async def test_tui_resumes_exact_approved_call_with_live_events(
|
|
223
|
+
tmp_path,
|
|
224
|
+
monkeypatch,
|
|
225
|
+
):
|
|
226
|
+
class Runtime:
|
|
227
|
+
async def __aenter__(self):
|
|
228
|
+
return self
|
|
229
|
+
|
|
230
|
+
async def __aexit__(self, *exc_info):
|
|
231
|
+
pass
|
|
232
|
+
|
|
233
|
+
async def resume(self, session_id, run_id, **kwargs):
|
|
234
|
+
assert session_id == "session-1"
|
|
235
|
+
assert run_id == "run-1"
|
|
236
|
+
assert kwargs["approved_call_ids"] == frozenset({"call-1"})
|
|
237
|
+
assert kwargs["permissions"] == frozenset({"notes.write"})
|
|
238
|
+
await kwargs["event_callback"](
|
|
239
|
+
RunEvent(
|
|
240
|
+
2,
|
|
241
|
+
RunEventType.RUN_RESUMED,
|
|
242
|
+
"run-1",
|
|
243
|
+
{"checkpoint_status": "resuming", "pending_call_id": "call-1"},
|
|
244
|
+
session_id="session-1",
|
|
245
|
+
)
|
|
246
|
+
)
|
|
247
|
+
return SimpleNamespace(
|
|
248
|
+
session=SimpleNamespace(session_id="session-1"),
|
|
249
|
+
result=RunResult(
|
|
250
|
+
"run-1",
|
|
251
|
+
StopReason.COMPLETED,
|
|
252
|
+
"approved",
|
|
253
|
+
(ModelMessage.text(MessageRole.ASSISTANT, "approved"),),
|
|
254
|
+
(),
|
|
255
|
+
steps=2,
|
|
256
|
+
tool_calls=1,
|
|
257
|
+
),
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
runtime = Runtime()
|
|
261
|
+
monkeypatch.setattr(tui_module, "load_local_runtime_config", lambda path: object())
|
|
262
|
+
monkeypatch.setattr(tui_module, "load_tool_entries", lambda entries: {})
|
|
263
|
+
monkeypatch.setattr(
|
|
264
|
+
tui_module,
|
|
265
|
+
"assemble_local_runtime",
|
|
266
|
+
lambda config, state_root, *, tool_bindings: runtime,
|
|
267
|
+
)
|
|
268
|
+
app = WAgentTui(tmp_path)
|
|
269
|
+
|
|
270
|
+
async with app.run_test(size=(150, 75)) as pilot:
|
|
271
|
+
app.query_one(TabbedContent).active = "checkpoints"
|
|
272
|
+
await pilot.pause()
|
|
273
|
+
app.query_one("#checkpoint-session").value = "session-1"
|
|
274
|
+
app.query_one("#checkpoint-run").value = "run-1"
|
|
275
|
+
app.query_one("#checkpoint-call").value = "call-1"
|
|
276
|
+
app.query_one("#checkpoint-tool-entries").value = "my_tools:bindings"
|
|
277
|
+
app.query_one("#checkpoint-permissions").value = "notes.write"
|
|
278
|
+
app.query_one("#checkpoint-tool-confirm").value = "LOAD TOOLS"
|
|
279
|
+
app.query_one("#checkpoint-confirm").value = "RESUME"
|
|
280
|
+
await pilot.click("#checkpoint-resume")
|
|
281
|
+
await pilot.pause()
|
|
282
|
+
|
|
283
|
+
assert "Stop: completed" in str(
|
|
284
|
+
app.query_one("#checkpoint-resume-result").content
|
|
285
|
+
)
|
|
286
|
+
events = str(app.query_one("#checkpoint-events").content)
|
|
287
|
+
assert "run-resumed" in events
|
|
288
|
+
assert "pending_call_id=call-1" in events
|
|
289
|
+
assert app.query_one("#checkpoint-confirm").value == ""
|
|
290
|
+
assert app.query_one("#checkpoint-tool-confirm").value == ""
|
|
291
|
+
|
|
292
|
+
|
|
112
293
|
@pytest.mark.asyncio
|
|
113
294
|
async def test_tui_active_provider_probe_requires_explicit_confirmation(tmp_path):
|
|
114
295
|
app = WAgentTui(tmp_path)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""W-Agent 框架包"""
|
|
2
2
|
|
|
3
|
-
__version__ = "2.0.
|
|
3
|
+
__version__ = "2.0.0a3"
|
|
4
4
|
__author__ = "LuckyStar2456"
|
|
5
5
|
__license__ = "MIT"
|
|
6
6
|
|
|
@@ -117,10 +117,11 @@ from w_agent.sandbox import (
|
|
|
117
117
|
UnsafeLocalSandboxProvider,
|
|
118
118
|
WorkspaceAccess,
|
|
119
119
|
)
|
|
120
|
-
from w_agent.sessions import (
|
|
121
|
-
InMemorySessionStore,
|
|
122
|
-
JsonSessionStore,
|
|
123
|
-
|
|
120
|
+
from w_agent.sessions import (
|
|
121
|
+
InMemorySessionStore,
|
|
122
|
+
JsonSessionStore,
|
|
123
|
+
RunEventCallback,
|
|
124
|
+
SessionError,
|
|
124
125
|
SessionManager,
|
|
125
126
|
SessionMessage,
|
|
126
127
|
SessionRecord,
|
|
@@ -747,8 +748,9 @@ __all__ = [
|
|
|
747
748
|
"workflow_resume_tool",
|
|
748
749
|
"workflow_start_tool",
|
|
749
750
|
"InMemorySessionStore",
|
|
750
|
-
"JsonSessionStore",
|
|
751
|
-
"
|
|
751
|
+
"JsonSessionStore",
|
|
752
|
+
"RunEventCallback",
|
|
753
|
+
"SessionError",
|
|
752
754
|
"SessionManager",
|
|
753
755
|
"SessionMessage",
|
|
754
756
|
"SessionRecord",
|
|
@@ -39,7 +39,12 @@ from w_agent.models import (
|
|
|
39
39
|
WeightedRoutingPolicy,
|
|
40
40
|
builtin_provider_template_registry,
|
|
41
41
|
)
|
|
42
|
-
from w_agent.sessions import
|
|
42
|
+
from w_agent.sessions import (
|
|
43
|
+
JsonSessionStore,
|
|
44
|
+
RunEventCallback,
|
|
45
|
+
SessionManager,
|
|
46
|
+
SessionRecord,
|
|
47
|
+
)
|
|
43
48
|
from w_agent.tools import ToolBinding, ToolExecutionContext, ToolExecutor, ToolRegistry
|
|
44
49
|
|
|
45
50
|
_COMPATIBLE_TEMPLATES = frozenset({"deepseek", "glm", "qwen", "turbo"})
|
|
@@ -260,6 +265,7 @@ class LocalAgentRuntime:
|
|
|
260
265
|
permissions: frozenset[str] = frozenset(),
|
|
261
266
|
approved_call_ids: frozenset[str] = frozenset(),
|
|
262
267
|
cancellation: CancellationToken | None = None,
|
|
268
|
+
event_callback: RunEventCallback | None = None,
|
|
263
269
|
) -> LocalRuntimeRun:
|
|
264
270
|
self._ensure_open()
|
|
265
271
|
if not prompt:
|
|
@@ -279,6 +285,7 @@ class LocalAgentRuntime:
|
|
|
279
285
|
approved_call_ids=approved_call_ids,
|
|
280
286
|
cancellation=cancellation,
|
|
281
287
|
),
|
|
288
|
+
event_callback=event_callback,
|
|
282
289
|
)
|
|
283
290
|
return LocalRuntimeRun(await self.sessions.get(session.session_id), result)
|
|
284
291
|
|
|
@@ -290,6 +297,7 @@ class LocalAgentRuntime:
|
|
|
290
297
|
permissions: frozenset[str] = frozenset(),
|
|
291
298
|
approved_call_ids: frozenset[str],
|
|
292
299
|
cancellation: CancellationToken | None = None,
|
|
300
|
+
event_callback: RunEventCallback | None = None,
|
|
293
301
|
) -> LocalRuntimeRun:
|
|
294
302
|
"""Resume one persisted approval checkpoint with explicit authority."""
|
|
295
303
|
|
|
@@ -306,6 +314,7 @@ class LocalAgentRuntime:
|
|
|
306
314
|
cancellation=cancellation,
|
|
307
315
|
),
|
|
308
316
|
cancellation=cancellation,
|
|
317
|
+
event_callback=event_callback,
|
|
309
318
|
)
|
|
310
319
|
return LocalRuntimeRun(await self.sessions.get(session_id), result)
|
|
311
320
|
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
+
import inspect
|
|
6
7
|
import json
|
|
7
8
|
import math
|
|
8
9
|
import os
|
|
9
10
|
import re
|
|
10
|
-
from collections.abc import Iterable
|
|
11
|
+
from collections.abc import Awaitable, Callable, Iterable
|
|
11
12
|
from dataclasses import dataclass, field, replace
|
|
12
13
|
from datetime import UTC, datetime
|
|
13
14
|
from enum import StrEnum
|
|
@@ -17,7 +18,14 @@ from types import MappingProxyType
|
|
|
17
18
|
from typing import Any, Mapping, Protocol
|
|
18
19
|
from uuid import uuid4
|
|
19
20
|
|
|
20
|
-
from w_agent.agents import
|
|
21
|
+
from w_agent.agents import (
|
|
22
|
+
AgentDefinition,
|
|
23
|
+
AgentExecution,
|
|
24
|
+
AgentLoop,
|
|
25
|
+
RunContext,
|
|
26
|
+
RunEvent,
|
|
27
|
+
RunResult,
|
|
28
|
+
)
|
|
21
29
|
from w_agent.kernel import ScopePath
|
|
22
30
|
from w_agent.models import (
|
|
23
31
|
CancellationToken,
|
|
@@ -128,6 +136,9 @@ class ResumableAgentLoop(Protocol):
|
|
|
128
136
|
) -> RunResult: ...
|
|
129
137
|
|
|
130
138
|
|
|
139
|
+
RunEventCallback = Callable[[RunEvent], Awaitable[None] | None]
|
|
140
|
+
|
|
141
|
+
|
|
131
142
|
class InMemorySessionStore:
|
|
132
143
|
def __init__(self) -> None:
|
|
133
144
|
self._sessions: dict[str, SessionRecord] = {}
|
|
@@ -276,6 +287,7 @@ class SessionManager:
|
|
|
276
287
|
cancellation: CancellationToken | None = None,
|
|
277
288
|
tool_context: ToolExecutionContext | None = None,
|
|
278
289
|
metadata: Mapping[str, Any] | None = None,
|
|
290
|
+
event_callback: RunEventCallback | None = None,
|
|
279
291
|
) -> RunResult:
|
|
280
292
|
current = tuple(messages)
|
|
281
293
|
if not current:
|
|
@@ -283,18 +295,22 @@ class SessionManager:
|
|
|
283
295
|
session = await self._active(session_id)
|
|
284
296
|
history = await self.conversation(session_id) if include_history else ()
|
|
285
297
|
resolved_run_id = run_id or f"run-{uuid4().hex}"
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
session_id=session_id,
|
|
295
|
-
metadata=metadata or {},
|
|
296
|
-
),
|
|
298
|
+
context = RunContext(
|
|
299
|
+
resolved_run_id,
|
|
300
|
+
(*history, *current),
|
|
301
|
+
scope=scope or ScopePath.application(),
|
|
302
|
+
cancellation=cancellation,
|
|
303
|
+
tool_context=tool_context or ToolExecutionContext(),
|
|
304
|
+
session_id=session_id,
|
|
305
|
+
metadata=metadata or {},
|
|
297
306
|
)
|
|
307
|
+
if event_callback is None:
|
|
308
|
+
result = await loop.run(definition, context)
|
|
309
|
+
else:
|
|
310
|
+
result = await _consume_execution(
|
|
311
|
+
loop.stream(definition, context),
|
|
312
|
+
event_callback,
|
|
313
|
+
)
|
|
298
314
|
if result.run_id != resolved_run_id:
|
|
299
315
|
raise SessionError("agent returned a different run id")
|
|
300
316
|
await self._record(session, definition, current, result, append_input=True)
|
|
@@ -308,15 +324,31 @@ class SessionManager:
|
|
|
308
324
|
*,
|
|
309
325
|
tool_context: ToolExecutionContext,
|
|
310
326
|
cancellation: CancellationToken | None = None,
|
|
327
|
+
event_callback: RunEventCallback | None = None,
|
|
311
328
|
) -> RunResult:
|
|
312
329
|
session = await self._active(session_id)
|
|
313
330
|
if not any(item.run_id == run_id for item in session.runs):
|
|
314
331
|
raise SessionError("run does not belong to session")
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
332
|
+
if event_callback is None:
|
|
333
|
+
result = await loop.resume(
|
|
334
|
+
run_id,
|
|
335
|
+
tool_context=tool_context,
|
|
336
|
+
cancellation=cancellation,
|
|
337
|
+
)
|
|
338
|
+
else:
|
|
339
|
+
resume_stream = getattr(loop, "resume_stream", None)
|
|
340
|
+
if resume_stream is None:
|
|
341
|
+
raise SessionError(
|
|
342
|
+
"agent loop does not support streamed approval resume"
|
|
343
|
+
)
|
|
344
|
+
result = await _consume_execution(
|
|
345
|
+
resume_stream(
|
|
346
|
+
run_id,
|
|
347
|
+
tool_context=tool_context,
|
|
348
|
+
cancellation=cancellation,
|
|
349
|
+
),
|
|
350
|
+
event_callback,
|
|
351
|
+
)
|
|
320
352
|
if result.run_id != run_id:
|
|
321
353
|
raise SessionError("resumed agent returned a different run id")
|
|
322
354
|
await self._record(
|
|
@@ -386,6 +418,21 @@ class SessionManager:
|
|
|
386
418
|
)
|
|
387
419
|
|
|
388
420
|
|
|
421
|
+
async def _consume_execution(
|
|
422
|
+
execution: AgentExecution,
|
|
423
|
+
event_callback: RunEventCallback,
|
|
424
|
+
) -> RunResult:
|
|
425
|
+
"""Consume one public event stream while projecting each event to a host."""
|
|
426
|
+
|
|
427
|
+
async for event in execution:
|
|
428
|
+
callback_result = event_callback(event)
|
|
429
|
+
if inspect.isawaitable(callback_result):
|
|
430
|
+
await callback_result
|
|
431
|
+
if execution.result is None:
|
|
432
|
+
raise SessionError("agent event stream completed without a result")
|
|
433
|
+
return execution.result
|
|
434
|
+
|
|
435
|
+
|
|
389
436
|
def _project_messages(
|
|
390
437
|
messages: Iterable[ModelMessage],
|
|
391
438
|
run_id: str,
|
|
@@ -63,7 +63,7 @@ class McpInputRequiredError(McpError):
|
|
|
63
63
|
@dataclass(frozen=True, slots=True)
|
|
64
64
|
class McpClientInfo:
|
|
65
65
|
name: str = "w-agent"
|
|
66
|
-
version: str = "2.0.
|
|
66
|
+
version: str = "2.0.0a3"
|
|
67
67
|
|
|
68
68
|
def __post_init__(self) -> None:
|
|
69
69
|
if not self.name.strip() or not self.version.strip():
|