Skip to content

Feature/yuchen.liu/mcp skills resources provider - #14

Merged
Leoyzen merged 29 commits into
develop/agenticfrom
feature/yuchen.liu/mcp-skills-resources-provider
Apr 13, 2026
Merged

Feature/yuchen.liu/mcp skills resources provider#14
Leoyzen merged 29 commits into
develop/agenticfrom
feature/yuchen.liu/mcp-skills-resources-provider

Conversation

@Leoyzen

@Leoyzen Leoyzen commented Apr 10, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

@Leoyzen
Leoyzen changed the base branch from main to develop/agentic April 10, 2026 06:14

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces several significant changes, including the implementation of concurrent execution safety for agents, the addition of slash command support for skills, and updates to the OpenCode server to support multi-question elicitation. My review identified a critical bug where registry_id was removed from the ClaudeCodeAgent constructor but remained in the from_config method, which will cause runtime errors. Additionally, I have concerns about the removal of E2E test artifacts from .gitignore, as this may lead to committing unnecessary or sensitive files.

I am having trouble creating individual review comments. Click here to see my feedback.

src/agentpool/agents/claude_code_agent/claude_code_agent.py (171)

critical

The variable _registry_id was removed from the constructor but not from the from_config method. This will cause a TypeError when from_config is called.

return cls(
            command=config.get_command(),
            args=config.get_args(),
            # Identity
            name=config.name,
            description=config.description,

.gitignore (1-3)

medium

Removing the E2E test artifacts from .gitignore might lead to committing large or sensitive test artifacts to the repository. Please ensure these artifacts are not intended to be ignored.

Leoyzen added 5 commits April 10, 2026 18:36
OpenCode sends skill commands without the 'skill:' prefix (e.g.,
'systematic_troubleshooting' instead of 'skill:systematic_troubleshooting').

Changes:
- Remove 'skill:' prefix from skill command names in skill_bridge.py
- Update session_routes.py to detect skill commands by looking them up
  in pool.skill_commands instead of checking for 'skill:' prefix
- Fix TextPart parameter names (message_id/session_id vs messageID/sessionID)

This aligns AgentPool with OpenCode's actual protocol behavior where
skills are first-class commands without prefixes.
OpenCode requires commands to have 'source' field (command | mcp | skill)
and 'template' field for skill commands (SKILL.md content).

Changes:
- Add source: Literal[command, mcp, skill] to Command model
- Add template: str to Command model for skill content
- Update skill_bridge to store SkillCommand objects for template access
- Update list_commands to include source=skill and template for skills

This enables OpenCode to:
1. Display skill badges in slash command picker
2. Load skill template when skill command is selected
…ilable

When pool.skill_commands is None (skill command registry not initialized),
but skills are available via pool.skill_provider (MCP provider), the
GET /command endpoint was returning empty list.

Now list_commands falls back to fetching skills directly from
skill_provider when skill_bridge is not available.

This ensures skills from MCP providers are exposed as slash commands
in OpenCode even when the skill command registry is not initialized.
This commit implements the complete RFC-0020 specification for loading skills via skill:// URIs from MCP providers.

Core Features:

- skill:// URI scheme support with provider/skill/reference paths

- AggregatingResourceProvider for combining local + MCP skill sources

- LocalResourceProvider with filesystem caching

- Extended MCPResourceProvider with skill:// resource discovery

- Argument substitution ($1, $2, \, \)

- Security: path traversal protection, null byte detection

Bug Fixes:

- Fix skill description from SKILL.md frontmatter

- Fix content loading via get_skill_instructions()

- Support underscore in skill names (e.g., diagnosis_planning)

- Handle PurePosixPath for skill:// URIs

Tests: 429 new tests (security, performance, integration, E2E)

Refs: RFC-0020
@Leoyzen

Leoyzen commented Apr 10, 2026

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements the MCP Skills Resources Provider protocol, enabling AgentPool to consume and provide skills via MCP resources and the skill:// URI scheme. While the implementation provides a solid foundation for unified skill access, several issues were identified: the load_skill tool fails to pass the pool context for reference loading and incorrectly handles bare name resolution, the SkillURIResolver has a regex mismatch with the Skill model regarding underscores, and there are priority inversion issues in _sync_commands and the OpenCode route handler. Additionally, performance and correctness improvements are needed for MCP skill discovery and instruction fetching.

Comment thread src/agentpool_toolsets/builtin/skills.py Outdated
Comment thread src/agentpool_toolsets/builtin/skills.py Outdated
Comment thread src/agentpool/skills/uri_resolver.py Outdated
Comment thread src/agentpool/skills/command_registry.py
Comment thread src/agentpool_server/opencode_server/routes/agent_routes.py Outdated
Comment thread src/agentpool/resource_providers/local.py Outdated
Comment thread src/agentpool/resource_providers/mcp_provider.py Outdated
Comment thread src/agentpool/resource_providers/mcp_provider.py Outdated
Comment thread src/agentpool/skills/skill.py Outdated
Leoyzen added 13 commits April 10, 2026 21:43
When using bare skill name (not URI), load_skill only checked
pool.skills (local filesystem skills), missing MCP-based skills
in pool.skill_provider.

Now load_skill:
1. Checks both pool.skills and pool.skill_provider
2. Loads instructions from appropriate source

This allows agents to load MCP-based skills by name.
list_skills only checked pool.skills (local skills), missing MCP-based
skills from pool.skill_provider.

Changes:
1. list_skills now checks both pool.skills and pool.skill_provider
2. Added comprehensive tests for MCP skills integration
   - test_list_skills_includes_mcp_skills
   - test_load_skill_finds_mcp_skills
   - test_load_skill_returns_error_for_missing_skill
   - test_list_skills_shows_empty_when_no_skills
   - test_load_skill_with_uri

Run tests: uv run pytest tests/skills/test_mcp_skills_integration.py -v
Skill names must use hyphens (-) not underscores (_).
Updated test to use 'systematic-troubleshooting' format.

All 5 tests now pass:
- test_list_skills_includes_mcp_skills ✓
- test_load_skill_finds_mcp_skills ✓
- test_load_skill_returns_error_for_missing_skill ✓
- test_list_skills_shows_empty_when_no_skills ✓
- test_load_skill_with_uri ✓
uri_resolver.py validation only allowed hyphens, but skill.py allowed
both hyphens and underscores. This caused MCP skills with underscores
in their names to fail validation.

Now both files consistently allow: lowercase letters, digits, hyphens,
and underscores in skill names.
Added test_load_skill_finds_mcp_skills_with_underscore to verify
that skills with underscores in their names can be loaded.

All 6 tests pass:
- test_list_skills_includes_mcp_skills ✓
- test_load_skill_finds_mcp_skills_with_hyphen ✓
- test_load_skill_finds_mcp_skills_with_underscore ✓
- test_load_skill_returns_error_for_missing_skill ✓
- test_list_skills_shows_empty_when_no_skills ✓
- test_load_skill_with_uri ✓
1. _load_reference_content now receives pool parameter for virtual paths
2. Bare skill names now use skill_resolver for proper provider search
   instead of manually checking pool.skills and pool.skill_provider

This ensures proper skill resolution with correct priority order.
1. _sync_commands now syncs MCP skills first, then local skills
   (local skills override MCP skills with same name)
2. list_skills now loads MCP skills first, then local skills
   (local skills take priority over MCP skills)

This ensures local skills always have higher priority as per RFC.
Implemented skills_changed signal emission in on_added and on_removed
callbacks using asyncio.create_task to handle async signal from sync
callback context.

This allows UI and other components to react to dynamic filesystem
changes without manual refresh.
Remove eager fetching of instructions during skill discovery.
Instructions are now lazy-loaded when load_instructions() is called
or when explicitly requested via get_skill_instructions().

This improves performance for servers with many skills.
For PurePosixPath skills (virtual/MCP-based), load instructions from
skill_provider instead of relying on skill.load_instructions() which
returns empty for virtual paths.

This fixes prompt-based skills not loading their instructions.
Added optional arguments parameter to get_skill_instructions for
prompt-based MCP skills. Arguments are passed to prompt rendering
to support server-side argument substitution.
Updated all provider implementations to include optional arguments
parameter in get_skill_instructions method:
- base.py: Updated abstract method signature
- aggregating.py: Passes arguments to child providers
- local.py: Accepts arguments (ignored for local skills)

This ensures consistency across all provider implementations.
Added mock implementations for:
- skill_resolver.resolve() to return appropriate skill based on URI
- skill_provider.get_skill_instructions() to return test instructions

All 6 tests now pass.
@Leoyzen

Leoyzen commented Apr 10, 2026

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements the MCP Skills Resources Provider protocol, enabling unified skill access via skill:// URIs for both local and MCP-sourced skills. It introduces a LocalResourceProvider, extends MCPResourceProvider to support skill discovery, and adds a SkillURIResolver for URI parsing and resolution. My review identified a critical infinite loop in the signal forwarding logic within AgentPool, a documentation gap regarding argument parsing, and a missing import in the documentation examples.

Comment thread src/agentpool/delegation/pool.py Outdated
Comment on lines +131 to +137
# Arguments are passed as a string
await load_skill(ctx, "greeting", "Alice Company formal")

# Result substitutes:
# $1 → "Alice"
# $2 → "Company"
# $3 → "formal"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The documentation on argument substitution should clarify how arguments containing spaces are handled. The current implementation splits arguments by spaces, which means a value like "Alice Smith" would be treated as two separate arguments, "Alice" and "Smith".

Please add a note explaining this behavior and suggest how to handle multi-word arguments, for example by using quotes if the parser supports it, or by noting it as a current limitation.

Comment thread docs/examples/mcp_skills/index.md
- Fix potential infinite loop in _on_skills_changed by removing dead event forwarding
- Raise ValueError in load_instructions for virtual skills without pre-set instructions
- Document argument space handling in skill-uri-usage.md
- Add missing json import in MCP skills example
@Leoyzen

Leoyzen commented Apr 10, 2026

Copy link
Copy Markdown
Collaborator Author

/gemini review

Update all callers of load_instructions() to properly handle virtual/MCP-based
skills by using skill_provider.get_skill_instructions() when available:

- agent_routes.py: Use provider for both skill_bridge and fallback paths
- session_routes.py: Use provider for skill instructions lookup
- skill_bridge.py: Add skill_provider parameter to OpenCodeSkillBridge
- server.py: Pass skill_provider when creating OpenCodeSkillBridge
@gemini-code-assist

Copy link
Copy Markdown

Warning

Gemini is experiencing higher than usual traffic and was unable to create the review. Please try again in a few hours by commenting /gemini review.

Add <uri> element to skill metadata format so agents know the correct
skill:// URI to use when referencing skills from MCP servers. This
improves the hit rate for skill loading by providing the full URI
instead of requiring the agent to guess the format.
1. Fix bare skill name loading in skills.py - use bare name directly with resolver

2. Fix virtual path detection - use type() instead of isinstance() since UPath is subclass of PurePosixPath

3. Fix skill provider setup - use existing resource_provider from SkillsManager
@Leoyzen

Leoyzen commented Apr 11, 2026

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements the skill:// URI scheme for AgentPool, enabling unified access to skills from local filesystems and MCP servers as specified in RFC-0020. It introduces a SkillURIResolver, a LocalResourceProvider, and extends the MCPResourceProvider to support both prompt-based and resource-based skills. The load_skill and list_skills tools have been updated to support URI resolution and argument substitution. Review feedback identified critical security improvements needed for path validation in both the URI resolver and the MCP resource provider, specifically to explicitly reject absolute paths and prevent potential path traversal vulnerabilities.

Comment on lines +705 to +730
# Path traversal protection
# Decode URL-encoded characters first
decoded_path = unquote(ref_path)

# Check for null bytes
if "\x00" in decoded_path:
raise SecurityError("Null bytes not allowed in path")

# Check for path traversal attempts
if ".." in decoded_path.split("/"):
raise SecurityError(f"Path traversal detected: {ref_path}")

# Normalize the path and ensure it's within references/
def _validate_path(path_str: str) -> Path:
"""Validate path and return resolved path."""
try:
return Path(path_str).resolve()
except Exception as e:
raise SecurityError(f"Invalid path: {ref_path}") from e

safe_path = _validate_path(decoded_path)
# The resolved path should not escape the references directory
if safe_path.parts and safe_path.parts[0] == "..":
raise SecurityError(f"Path escapes references directory: {ref_path}")

# Construct the full URI

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The path validation in read_reference is insufficient and could allow an attacker to read arbitrary files on the server. The current implementation checks for .. but does not properly handle absolute paths. For example, a ref_path of /etc/passwd would bypass the checks because Path('/etc/passwd').resolve() returns an absolute path that doesn't start with ...

The use of Path(path_str).resolve() is also problematic for a remote resource, as it attempts to resolve a path on the local filesystem, which is not the correct approach for a URI-based resource.

Suggested change
# Path traversal protection
# Decode URL-encoded characters first
decoded_path = unquote(ref_path)
# Check for null bytes
if "\x00" in decoded_path:
raise SecurityError("Null bytes not allowed in path")
# Check for path traversal attempts
if ".." in decoded_path.split("/"):
raise SecurityError(f"Path traversal detected: {ref_path}")
# Normalize the path and ensure it's within references/
def _validate_path(path_str: str) -> Path:
"""Validate path and return resolved path."""
try:
return Path(path_str).resolve()
except Exception as e:
raise SecurityError(f"Invalid path: {ref_path}") from e
safe_path = _validate_path(decoded_path)
# The resolved path should not escape the references directory
if safe_path.parts and safe_path.parts[0] == "..":
raise SecurityError(f"Path escapes references directory: {ref_path}")
# Construct the full URI
# Path traversal protection
# Decode URL-encoded characters first
decoded_path = unquote(ref_path)
# Check for null bytes
if "\x00" in decoded_path:
raise SecurityError("Null bytes not allowed in path")
# Check for path traversal attempts and absolute paths
if ".." in decoded_path.split("/") or decoded_path.startswith("/"):
raise SecurityError(f"Path traversal detected: {ref_path}")

Comment on lines +145 to +152
# Validate reference path components if present
if reference_path is not None:
ref_parts = reference_path.split("/")
for part in ref_parts:
if part == "..":
msg = f"Path traversal detected in reference path: {uri!r}"
raise SecurityError(msg)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The path validation for reference_path checks for .. and null bytes, but it does not prevent absolute paths (paths starting with /). While the provider might handle this, it's better to enforce this at the URI parsing level for defense-in-depth. An absolute path in a URI reference is ambiguous and a potential security risk.

Suggested change
# Validate reference path components if present
if reference_path is not None:
ref_parts = reference_path.split("/")
for part in ref_parts:
if part == "..":
msg = f"Path traversal detected in reference path: {uri!r}"
raise SecurityError(msg)
if reference_path is not None:
if ".." in reference_path.split("/") or reference_path.startswith("/"):
msg = f"Path traversal detected in reference path: {uri!r}"
raise SecurityError(msg)

Add checks for absolute paths (starting with /) in addition to .. traversal:

1. mcp_provider.py read_reference: Reject paths starting with /

2. uri_resolver.py: Reject reference paths starting with /

This prevents attackers from reading arbitrary files like /etc/passwd
@Leoyzen

Leoyzen commented Apr 12, 2026

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements RFC-0020, introducing a unified skill:// URI scheme to load skills from both the local filesystem and MCP servers. Key additions include a SkillURIResolver, a LocalResourceProvider, and extensions to the MCPResourceProvider to support both prompt-based and resource-based skills. The load_skill and list_skills tools were updated to support URIs, reference content, and bash-style argument substitution. Feedback highlights several critical issues, including a path traversal vulnerability in reference loading, sync/async mismatches in provider initialization, and return type inconsistencies in the read_reference interface. Further improvements were suggested regarding URI parsing logic, ensuring skill priority is respected during dynamic re-syncs, and adding missing method definitions to the base ResourceProvider class for better type safety.

Comment thread src/agentpool_toolsets/builtin/skills.py Outdated
Comment thread src/agentpool/delegation/pool.py Outdated
Comment thread src/agentpool/delegation/pool.py
Comment thread src/agentpool/resource_providers/mcp_provider.py Outdated
Comment thread src/agentpool/resource_providers/mcp_provider.py Outdated
Comment thread src/agentpool/skills/command_registry.py Outdated
Comment thread src/agentpool/skills/command_registry.py Outdated
Comment thread src/agentpool/resource_providers/local.py Outdated
Returns:
Self for context manager protocol
"""
await self._registry.discover_skills()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If SkillsRegistry.discover_skills() is a synchronous method (as suggested by the RFC documentation), calling it with await will fail if it's not a coroutine, or block the event loop if it is a blocking sync call. You should use anyio.to_thread.run_sync() to run it in a separate thread, as described in the RFC's technical design.

Comment on lines 141 to 154
"""Get available instruction functions. Override to provide instructions."""
return []

async def get_skill_instructions(self, skill_name: str) -> str:
async def get_skill_instructions(
self, skill_name: str, arguments: dict[str, str] | None = None
) -> str:
"""Get full instructions for a specific skill.

Args:
skill_name: Name of the skill to get instructions for
arguments: Optional arguments for prompt-based skills

Returns:
The full skill instructions for execution

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The ResourceProvider base class is missing definitions for get_skills, get_skill, get_references, and read_reference, even though these are implemented in subclasses and used polymorphically (e.g., in AggregatingResourceProvider and the load_skill tool). Adding these to the base class with default implementations would improve type safety and clarity.

Leoyzen added 5 commits April 13, 2026 09:40
1. Fix path traversal in skills.py _load_reference_content

2. Make _setup_skills_provider async in pool.py

3. Use PurePosixPath for virtual URIs in mcp_provider.py

4. Fix read_reference return type to tuple[bytes, str]

5. Add read_reference to AggregatingResourceProvider
1. Fix command_registry.py to call _sync_commands() for proper priority

2. Use actual provider name from skill metadata for skill_uri
1. local.py: Use rglob for recursive reference search

2. base.py: Add missing skill-related methods (get_skills, get_skill, get_references, read_reference)
@Leoyzen
Leoyzen merged commit ee98cc1 into develop/agentic Apr 13, 2026
Leoyzen added a commit that referenced this pull request May 27, 2026
- providers/set & providers/disable: 兼容 id 字段(Comment #12, #13)
- provider_router: 防御性初始化 + 未知 provider 静默禁用(Comment #14, #15)
- model_utils: 先过滤 raw toko_models(更准确),current_model 不在列表时插入(Comment #16)
- .gitignore: 添加 .omo/(Comment #18
Leoyzen added a commit that referenced this pull request May 27, 2026
* docs(rfc): add RFC-0034 ACP Session Config Options 统一化

新增 RFC-0034,提案升级 AgentPool ACP Server 的 Session Config Options
透出逻辑,使 Zed 等 ACP 兼容 IDE 能够选择模型和切换 Agent Role。

主要内容:
- 识别 4 个 GAP:Agent Role 未透出(P0)、ACP/OpenCode model list
  数据来源不一致(P1)、/mode 路由硬编码(P1)、get_session_mode_state
  过滤过严(P2)
- 分析 3 个方案,推荐选项 2(三阶段统一化)
- 技术设计:build_model_state_for_acp()、get_agent_role_config_option()、
  _swap_session_agent() 及 OpenCode /mode 路由动态修复

🤖 Generated with [Qoder][https://qoder.com]

* docs(rfc): 根据 review 反馈修正 RFC-0034

- 修正 model fallback 逻辑:strict fallback(configured 存在时只用 configured)
- 修正 agent_role current_value:使用 agent.name 而非 pool.main_agent.name
- 重写 _swap_session_agent():委托 session.switch_active_agent() + _session_agent_locks 保护
- 增加 session._task_lock 协调:拒绝 active prompt 期间的 swap
- 增加 pool.manifest null check 保护
- 修正 list_modes() null safety:state.agent 为 None 时返回默认值
- 明确开放问题 Q2/Q3 的决策:对话历史不继承、current_value 已修复
- 更新决策记录:增加 session mutation 复用、锁保护、task_lock 协调、对话历史决策
- 更新 Phase 2 实施计划:增加 Zed 预验证、并发测试、current_value 测试
- 调整工作量估算:~260 行 → ~240 行

* docs(rfc): RFC-0034 新增 Phase 0 — ACP Configurable LLM Providers 适配

ACP PR #648 (Configurable LLM Providers) 已 MERGED,引入
providers/list、providers/set、providers/disable 三个方法族,
允许客户端发现和覆盖 agent 的 LLM 请求路由。

主要更新:
- 新增 GAP 5 (P0): providers/* 完全未实现
- 新增目标 G7: 实现 ACP providers/* 协议方法
- 新增 Phase 0: ProviderRouter 实现 + schema 类型定义 +
  ACP 请求处理器 + AgentCapabilities.providers 声明
- 修订 Phase 1: build_model_state_for_acp() 接受 provider_router
  参数,过滤被禁用 provider 下的模型
- 更新架构概览图: 传输层(providers)与应用层(session config)分层
- 更新里程碑: 四阶段实施,Phase 0 优先于 Phase 1
- 新增开放问题 6/7/8: providers 对已运行 session 的影响、
  provider 路由覆盖与 agent 初始化兼容、SessionModelState
  中是否携带 provider 关联信息
- 新增决策记录: providers/set 保守策略、从 model_variants
  派生 ProviderInfo、provider_router 参数解耦

🤖 Generated with [Qoder][https://qoder.com]

* docs(rfc): 优化 RFC-0034 — 补充 Zed 源码级兼容性分析

基于 Zed 源码调研(crates/agent_ui/src/config_options.rs、profile_selector.rs、
agent_servers/src/acp.rs)的关键发现:

1. Zed 渲染所有 config_options 为独立 UI 按钮,agent_role 可正确显示和点击
2. first_config_option_id() 仅返回同 category 的第一个 option,键盘快捷键
   可能冲突 — 标记为已知限制(NG7)
3. Zed ProfileSelector 完全独立于 ACP,使用本地 AgentSettings.profiles
4. Zed 当前完全不支持 providers/* 协议(Phase 0 暂无 Zed UI 入口)

RFC 更新内容:
- 新增 Zed IDE 渲染行为小节(源码级证据)
- 新增 Zed 兼容性分析总结表
- 更新非目标 NG7:键盘快捷键冲突为已知限制
- 更新开放问题 5/6/7/8/9,标记 Zed 调研结论
- 更新 Phase 2 预验证:明确键盘限制和排序建议
- 更新向后兼容保证表:添加 category 冲突行
- 更新决策记录:补充 Zed 调研证据

🤖 Generated with [Qoder][https://qoder.com]

* feat(acp): implement RFC-0034 ACP Session Config Options unification

Phase 0: ACP Configurable LLM Providers
- Add providers/* protocol methods (providers/list, providers/set, providers/disable)
- Add ProviderRouter with override/disable/capability tracking
- Add providers field to AgentCapabilities and InitializeResponse

Phase 1: Shared Model List Logic
- Add build_model_state_for_acp() with configured-first, tokonomics-fallback
- Invert get_session_model_state() to use configured variants first

Phase 2: Agent Role Config Option
- Add get_agent_role_config_option() exposing pool.all_agents
- Add _swap_session_agent() with lock protection
- Extend set_session_config_option() with agent_role handling

Phase 3: OpenCode /mode Route Fix
- Dynamic /mode route using agent.get_modes()

Also includes RFC-0033 MCP over ACP support:
- Add AcpMcpServer type and acp field to McpCapabilities
- Add acp_mcp_servers parameter to AgentCapabilities.create()

Tests:
- 35 new tests across provider_router, model_state, agent_role,
  config_routes, and cross-protocol integration
- Snapshot tests re-baselined

* chore: remove RFC-0033 code from RFC-0034 branch

Remove accidentally included RFC-0033 MCP-over-ACP implementation:
- Delete acp_mcp_manager.py, acp_mcp_transport.py
- Delete RFC-0033 tests (test_mcp.py, test_acp_mcp_*, test_mcp_integration)
- Remove AcpMcpServer from mcp.py
- Remove acp field from McpCapabilities
- Remove acp_mcp_servers parameter from AgentCapabilities.create()
- Remove acp_mcp_servers parameter from InitializeResponse.create()
- Remove RFC-0033 handler code from acp_agent.py

Keep RFC-0034 changes intact:
- providers/* protocol methods
- ProviderRouter with override/disable
- build_model_state_for_acp() configured-first logic
- agent_role config option and swap
- Dynamic /mode route

* fix: address PR review comments for RFC-0034

- _swap_session_agent: Update session agent registry after swap (review #5)
- get_agent_role_config_option: Use display_name with type-safe fallback, add description (review #7)
- list_modes: Use mode.id for programmatic identifiers, add explicit None guard (review #4, #8)
- Update tests to match new behavior

* fix(agent): use model_variants in get_modes() instead of tokonomics

Agent.get_modes() was calling get_available_models() which returns
all tokonomics-discovered models (2000+). Now it checks configured
model_variants first and only falls back to tokonomics when no
variants are configured.

Fixes the issue where config_options model selector showed thousands
of models instead of the configured variants.

* fix(agent): track model variant name to fix Zed Unknown display

When using model_variants, get_modes() returned variant names as option ids
but current_mode_id was the raw model identifier (e.g. openai:svc/glm-4.7).
This caused Zed to display 'Unknown' because current_mode_id didn't match
any available mode id.

Fix: Add _current_model_variant field to Agent. When _set_mode() is called
with a variant name, store it. get_modes() now uses _current_model_variant
as current_mode_id so it matches the option ids.

* fix(agent): set _current_model_variant on init when model is variant name

Agent.__init__ resolves model string via _resolve_model_string(), but
was not setting _current_model_variant. This caused get_modes() to fall
back to self.model_name (the raw model identifier) on initial load,
showing 'Unknown' in Zed until _set_mode() was called.

Fix: Also track variant name in __init__ when model string matches a
model_variants key.

* fix(agent): use actual model identifier as mode id, variant name as display name

Redesign model config option to use actual model identifiers:
- id/value: actual model identifier (e.g. openai:svc/glm-4.7)
- name: variant name (e.g. glm47) for display
- current_mode_id: actual model identifier

This ensures currentValue matches option values in Zed's config option
selector, fixing the 'Unknown' display issue.

_set_mode() now supports both actual model identifiers and variant names
by reverse-lookup from manifest model_variants.

* fix(agent): align get_modes() id format with model_name

Use config.get_model().system:model_name for option ids instead of
config.identifier, ensuring currentValue matches option values.

Root cause: model_name returns pydantic-ai system:model_name format
(e.g., 'openai:svc/glm-4.7') while config.identifier returns full
provider format (e.g., 'openai-chat:svc/glm-4.7'), causing mismatch
in Zed's model selector dropdown.

* fix: address PR #37 review comments (round 2)

- providers/set & providers/disable: 兼容 id 字段(Comment #12, #13)
- provider_router: 防御性初始化 + 未知 provider 静默禁用(Comment #14, #15)
- model_utils: 先过滤 raw toko_models(更准确),current_model 不在列表时插入(Comment #16)
- .gitignore: 添加 .omo/(Comment #18
Leoyzen added a commit that referenced this pull request Jun 3, 2026
- Wrap event_bus.unsubscribe() in try-except within finally block
  to prevent cleanup exceptions from masking original exceptions (Comment #10)
- Remove dead code conditional publish in agent.py: process_tool_event()
  already publishes to EventBus internally when run_ctx.event_bus is set,
  so the if combined: block never executes (Comment #13)
- Change EventBus subscription scope from "self" to "session" in
  subagent_tools.py for clarity and consistency (Comment #14)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant