Skip to content

feat(call-omo-agent): support custom agents via dynamic resolution#2299

Merged
code-yeongyu merged 12 commits intocode-yeongyu:devfrom
brandonwebb-vista:feat/dynamic-custom-agent-support
Apr 15, 2026
Merged

feat(call-omo-agent): support custom agents via dynamic resolution#2299
code-yeongyu merged 12 commits intocode-yeongyu:devfrom
brandonwebb-vista:feat/dynamic-custom-agent-support

Conversation

@brandonwebb-vista
Copy link
Copy Markdown
Contributor

@brandonwebb-vista brandonwebb-vista commented Mar 4, 2026

Summary

Replace the hardcoded ALLOWED_AGENTS whitelist validation in call_omo_agent with dynamic agent resolution via client.app.agents(), and extend the agent loader to discover agents from all configuration paths.

Closes #1706

Changes

Dynamic Agent Resolution (call-omo-agent)

  • agent-resolver.ts: New resolveCallableAgents() queries registered agents at execute-time and merges them with ALLOWED_AGENTS (built-in agents are always callable, never dropped)
  • tools.ts: Validate subagent_type against the dynamic set; fixed subagent_type parameter description
  • constants.ts: Updated description to be path-agnostic (covers all loader paths, not just ~/.claude/agents/)
  • tools.test.ts: 18 tests covering dynamic resolution, merge behavior, fallback, and validation

Agent Loader Extension (claude-code-agent-loader)

  • loader.ts: Added loadOpencodeGlobalAgents() (~/.config/opencode/agents/) and loadOpencodeProjectAgents() (<cwd>/.opencode/agents/)
  • types.ts: Extended AgentScope with "opencode" and "opencode-project"
  • loader.test.ts: 15 tests covering YAML/MD parsing, tool extraction, scope assignment, and env-var paths

Agent Config Handler Wiring (agent-config-handler.ts)

  • Calls all 4 loaders: user, project, opencode-global, opencode-project
  • OpenCode loaders run unconditionally (not gated by claude_code.agents config flag)
  • Merge priority: opencode-project > opencode-global > user > project > plugin

Test Results

  • bun test src/tools/call-omo-agent/: 18 pass, 0 fail
  • bun test src/features/claude-code-agent-loader/: 15 pass, 0 fail
  • bun run typecheck: clean
  • Full suite: 3540 pass, 5 fail (all 5 are pre-existing failures in unrelated files)

Agent Discovery Paths (after this PR)

Path Scope Loader
~/.claude/agents/ user loadUserAgents()
<cwd>/.claude/agents/ project loadProjectAgents()
~/.config/opencode/agents/ opencode loadOpencodeGlobalAgents()
<cwd>/.opencode/agents/ opencode-project loadOpencodeProjectAgents()

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 4, 2026

All contributors have signed the CLA. Thank you! ✅
Posted by the CLA Assistant Lite bot.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 4 files

Confidence score: 2/5

  • There is a high-confidence compatibility bug in src/tools/call-omo-agent/constants.ts: agent directories point to .claude/agents/ instead of Opencode paths (~/.config/opencode/agents/ and .opencode/agents/), which can prevent custom agents from being discovered at runtime.
  • src/tools/call-omo-agent/tools.ts also documents the wrong directories with high confidence, increasing the chance users configure the feature incorrectly and experience failures even if code paths are corrected elsewhere.
  • The subagent_type wording in src/tools/call-omo-agent/tools.ts is lower-confidence and lower-severity, but it still conflicts with dynamic agent resolution and may mislead usage.
  • Pay close attention to src/tools/call-omo-agent/constants.ts and src/tools/call-omo-agent/tools.ts - directory path compatibility and related guidance need to be aligned with Opencode behavior.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/tools/call-omo-agent/tools.ts">

<violation number="1" location="src/tools/call-omo-agent/tools.ts:19">
P2: Custom agent: **Opencode Compatibility**

The documentation incorrectly references Claude Code's agent directories (`~/.claude/agents/` and `.claude/agents/`) instead of Opencode's agent directories. According to the Opencode documentation, custom agents are loaded from `~/.config/opencode/agents/` and `.opencode/agents/`.</violation>

<violation number="2" location="src/tools/call-omo-agent/tools.ts:51">
P2: `subagent_type` description still claims "explore or librarian only" even though agents are now dynamically resolved; this contradicts the new capability and can mislead tool usage.</violation>
</file>

<file name="src/tools/call-omo-agent/constants.ts">

<violation number="1" location="src/tools/call-omo-agent/constants.ts:16">
P1: Custom agent: **Opencode Compatibility**

Incorrect agent directory paths. OpenCode loads custom agents from `~/.config/opencode/agents/` and `.opencode/agents/`, not `.claude/agents/`.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Add one-off context when rerunning by tagging @cubic-dev-ai with guidance or docs links (including llms.txt)
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/tools/call-omo-agent/constants.ts Outdated
Comment thread src/tools/call-omo-agent/tools.ts
Comment thread src/tools/call-omo-agent/tools.ts Outdated
@brandonwebb-vista
Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I hereby sign the CLA

github-actions bot added a commit that referenced this pull request Mar 4, 2026
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 18 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/plugin-handlers/agent-config-handler.ts">

<violation number="1" location="src/plugin-handlers/agent-config-handler.ts:209">
P2: Global agents overwrite project-level agents due to incorrect spread order. Project-level configuration should take precedence over global configuration, but the current order means global agents overwrite project agents.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/plugin-handlers/agent-config-handler.ts Outdated
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/plugin-handlers/agent-config-handler.ts">

<violation number="1" location="src/plugin-handlers/agent-config-handler.ts:209">
P1: Custom agent: **Opencode Compatibility**

The configuration merge priority violates OpenCode's standard configuration precedence, where project-level configurations must always override global/user-level configurations.

In OpenCode, local configurations always win. However, this PR's object spread order causes two precedence violations:
1. `userAgents` (global) incorrectly overrides `projectAgents` (local).
2. `opencodeGlobalAgents` (global) incorrectly overrides `projectAgents` (local).

To follow OpenCode's "Project > Global" approach while maintaining your "OpenCode format > Claude format" precedence, all global agents should be spread before any project agents. Note: This incorrect ordering appears in both `applyAgentConfig` return blocks, so the fix should be applied to both.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/plugin-handlers/agent-config-handler.ts Outdated
@brandonwebb-vista
Copy link
Copy Markdown
Contributor Author

The CLA agent seems to be broken - it's looking at a "@brandon Webb" as the user (which is not my account) instead of "@brandonwebb-vista"

@alexmhtop
Copy link
Copy Markdown

The CLA agent seems to be broken - it's looking at a "@brandon Webb" as the user (which is not my account) instead of "@brandonwebb-vista"

I think the first commit was done with the local git config which had another user than your GitHub one
image
Can you check it. Your feature is nice and I hope to see it implemented as well.

Thank you for the contribution! omo is amazing

@brandonwebb-vista
Copy link
Copy Markdown
Contributor Author

@alexmhtop I realized that my email wasn't configured correctly in git global, so that's the reason for the difference. Short of recreating this MR, I'm hoping we can just force skip the CLA Assistant? It should be clear that I did sign the CLA

@brandonwebb-vista
Copy link
Copy Markdown
Contributor Author

@code-yeongyu this MR should be ready to go

@tx1ee
Copy link
Copy Markdown

tx1ee commented Mar 11, 2026

Awesome feature, expect code merge 👍

@brandonwebb-vista
Copy link
Copy Markdown
Contributor Author

@code-yeongyu when can we expect to get this merged?

@tx1ee
Copy link
Copy Markdown

tx1ee commented Mar 20, 2026

@code-yeongyu when can we expect to get this merged?

Looking forward to this feature merger!!!

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/shared/agent-tool-restrictions.ts">

<violation number="1" location="src/shared/agent-tool-restrictions.ts:58">
P1: Custom agent: **Opencode Compatibility**

Revert the default fallback to `UNRESTRICTED` to allow custom dynamic agents to use write and edit tools.</violation>

<violation number="2" location="src/shared/agent-tool-restrictions.ts:58">
P2: Inconsistent fallback logic between `hasAgentToolRestrictions` and `getAgentToolRestrictions` for unknown agents. The `getAgentToolRestrictions` function returns `EXPLORATION_AGENT_DENYLIST` for unknown agents, but `hasAgentToolRestrictions` (which was not updated) returns `false` for the same input, creating a logical inconsistency.</violation>
</file>

<file name="src/tools/call-omo-agent/agent-resolver.ts">

<violation number="1" location="src/tools/call-omo-agent/agent-resolver.ts:24">
P1: Custom agent: **Opencode Compatibility**

Add `{ throwOnError: true }` to the SDK call so that actual API errors are caught and logged, rather than throwing a generic `TypeError` when `.filter()` is called on the error object.</violation>

<violation number="2" location="src/tools/call-omo-agent/agent-resolver.ts:36">
P2: Merging logic fails to preserve canonical casing from registry. The function iterates `[...ALLOWED_AGENTS, ...dynamicAgents]`, but ALLOWED_AGENTS contains lowercase names. When agents overlap, the lowercase version is added first and the registry's canonical casing is discarded due to case-insensitive deduplication. To honor the documented "Returns canonical names" contract, iterate `[...dynamicAgents, ...ALLOWED_AGENTS]` instead.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/shared/agent-tool-restrictions.ts Outdated
Comment thread src/tools/call-omo-agent/agent-resolver.ts Outdated
Comment thread src/shared/agent-tool-restrictions.ts Outdated
Comment thread src/tools/call-omo-agent/agent-resolver.ts Outdated
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/tools/call-omo-agent/agent-resolver.ts">

<violation number="1" location="src/tools/call-omo-agent/agent-resolver.ts:36">
P2: Dynamic agents with different casing can shadow built-in agents' canonical lowercase names. The spread order prioritizes dynamicAgents over ALLOWED_AGENTS, so when a dynamic agent shares a name (case-insensitively) with a built-in agent but uses different casing, the built-in agent's canonical name is omitted from the returned list. To preserve canonical naming for built-in agents, swap the spread order to `[...ALLOWED_AGENTS, ...dynamicAgents]`.</violation>
</file>

<file name="src/shared/agent-tool-restrictions.ts">

<violation number="1">
P1: Removal of fail-safe tool restrictions grants fully unrestricted tool access to unknown agents. The fallback in `getAgentToolRestrictions` was changed from `EXPLORATION_AGENT_DENYLIST` (which denied write/edit/task/call_omo_agent) to `{}` (no restrictions). This means any misspelled agent name, future unregistered agent, or validation bypass will silently receive unrestricted access to all tools including sensitive operations.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/shared/agent-tool-restrictions.ts
Comment thread src/tools/call-omo-agent/agent-resolver.ts Outdated
@code-yeongyu
Copy link
Copy Markdown
Owner

Addressing remaining cubic findings from latest review:

P1 (agent-tool-restrictions.ts ?? {} fallback): This is the pre-existing default on dev branch — the PR diff for this file is whitespace-only. Changing to EXPLORATION_AGENT_DENYLIST breaks 3 existing tests (sisyphus, plan, prometheus need unrestricted access). Previous cubic review (round 3) explicitly requested keeping {} for OpenCode compatibility. Custom agents are user-created and intentionally unrestricted. Not a regression from this PR.

P2 (merge order / canonical casing): Built-in agents in ALLOWED_AGENTS are all lowercase — which IS their canonical form. Dynamic agents from the registry preserve original casing. Current order [...dynamicAgents, ...ALLOWED_AGENTS] prioritizes registry casing when overlapping, which is correct (registry is authoritative).

P1 (throwOnError): client.app.agents() is used identically in delegate-task/subagent-resolver.ts without throwOnError. The try/catch block already handles SDK errors gracefully with fallback to ALLOWED_AGENTS. Consistent with existing codebase patterns.

@cubic-dev-ai please re-review

@cubic-dev-ai
Copy link
Copy Markdown

cubic-dev-ai bot commented Mar 20, 2026

Addressing remaining cubic findings from latest review:

P1 (agent-tool-restrictions.ts ?? {} fallback): This is the pre-existing default on dev branch — the PR diff for this file is whitespace-only. Changing to EXPLORATION_AGENT_DENYLIST breaks 3 existing tests (sisyphus, plan, prometheus need unrestricted access). Previous cubic review (round 3) explicitly requested keeping {} for OpenCode compatibility. Custom agents are user-created and intentionally unrestricted. Not a regression from this PR.

P2 (merge order / canonical casing): Built-in agents in ALLOWED_AGENTS are all lowercase — which IS their canonical form. Dynamic agents from the registry preserve original casing. Current order [...dynamicAgents, ...ALLOWED_AGENTS] prioritizes registry casing when overlapping, which is correct (registry is authoritative).
...

@code-yeongyu I have started the AI code review. It will take a few minutes to complete.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 10 files

Confidence score: 2/5

  • There is a high-confidence compatibility risk in src/tools/call-omo-agent/tools.test.ts: the mock shape for client.app.agents() (array) does not match the Opencode SDK response contract (data object), which can mask real integration failures.
  • src/plugin-handlers/agent-config-handler.ts appears to invert customAgentSummaries merge order versus SDK priority, so system prompts may use overridden/incorrect agent descriptions at runtime.
  • Given two concrete, high-severity (8/10) Opencode compatibility issues plus a validation gap in src/tools/call-omo-agent/agent-resolver.ts (name not validated), this carries meaningful regression risk before merge.
  • Pay close attention to src/plugin-handlers/agent-config-handler.ts, src/tools/call-omo-agent/tools.test.ts, src/tools/call-omo-agent/agent-resolver.ts - SDK contract/order mismatches and missing name validation can break agent resolution behavior.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/tools/call-omo-agent/agent-resolver.ts">

<violation number="1" location="src/tools/call-omo-agent/agent-resolver.ts:30">
P2: Missing validation on agent `name` property can cause dynamic resolution failure</violation>
</file>

<file name="src/tools/call-omo-agent/tools.test.ts">

<violation number="1" location="src/tools/call-omo-agent/tools.test.ts:10">
P1: Custom agent: **Opencode Compatibility**

The mock for `client.app.agents()` returns an array directly, but the Opencode SDK returns a response object with a `data` property.</violation>
</file>

<file name="src/plugin-handlers/agent-config-handler.ts">

<violation number="1" location="src/plugin-handlers/agent-config-handler.ts:105">
P1: Custom agent: **Opencode Compatibility**

The `customAgentSummaries` array order is inverted compared to the actual SDK merge priority, causing the system prompt to receive overridden agent descriptions instead of the active ones.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/tools/call-omo-agent/tools.test.ts Outdated
Comment thread src/plugin-handlers/agent-config-handler.ts Outdated
Comment thread src/tools/call-omo-agent/agent-resolver.ts Outdated
@code-yeongyu code-yeongyu added the triage:feature-request Feature or enhancement request label Mar 24, 2026
@weijer
Copy link
Copy Markdown

weijer commented Mar 25, 2026

Really need this feature. When can the current PR be merged

@brandonwebb-vista brandonwebb-vista force-pushed the feat/dynamic-custom-agent-support branch from 0d818c8 to 27bce77 Compare March 26, 2026 14:32
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/shared/agent-tool-restrictions.ts">

<violation number="1" location="src/shared/agent-tool-restrictions.ts:50">
P2: `getAgentToolRestrictions` now defaults unknown agents to a denylist, but `hasAgentToolRestrictions` still returns false for those same agents, creating an inconsistent exported API that can bypass restriction gating.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/shared/agent-tool-restrictions.ts Outdated
@brandonwebb-vista brandonwebb-vista force-pushed the feat/dynamic-custom-agent-support branch from aaba861 to 1e759ab Compare March 26, 2026 14:52
@brandonwebb-vista
Copy link
Copy Markdown
Contributor Author

@code-yeongyu can we merge this?

@tx1ee
Copy link
Copy Markdown

tx1ee commented Apr 3, 2026

@code-yeongyu can we merge this?

I am also waiting for this PR merger

@brandonwebb-vista
Copy link
Copy Markdown
Contributor Author

brandonwebb-vista commented Apr 7, 2026

@code-yeongyu can we merge? it's becoming a chore to continually come back and update/resolve merge conflicts

brandonwebb-vista added a commit to brandonwebb-vista/oh-my-opencode that referenced this pull request Apr 14, 2026
…d JSON agent loader

Wave 1 of agent definitions enhancement (PR code-yeongyu#2299):

Schema & Configuration:
- Add agent_definitions field to oh-my-opencode config schema
- Support list of file paths to .md or .json agent definition files
- Add to PARTIAL_STRING_ARRAY_KEYS for Set-union merge semantics
- Implement eager path resolution in loadPluginConfig() before merging

Path Resolution:
- Create resolve-agent-definition-paths.ts helper
- User-level paths resolve from ~/.config/opencode/ (no containment)
- Project-level paths resolve from project root (with containment check)
- Homedir expansion, absolute/relative path handling

JSON Agent Loader:
- Create parseJsonAgentFile() for .json/.jsonc agent definitions
- Validate required fields (name, prompt)
- Support tools as string (comma-separated) or array
- Map model via mapClaudeModelToOpenCode()
- Comprehensive test suite (7 test cases, all passing)

Type Extensions:
- Extend AgentScope: add 'definition-file' and 'opencode-config'
- Add AgentJsonDefinition interface for JSON agent schema

All automated checks passing:
- lsp_diagnostics clean on all changed files
- json-agent-loader.test.ts: 7/7 passing
- Full typecheck: zero new errors
- QA evidence saved to .sisyphus/evidence/
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

5 issues found across 16 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/features/claude-code-agent-loader/agent-definitions-loader.ts">

<violation number="1" location="src/features/claude-code-agent-loader/agent-definitions-loader.ts:31">
P2: Markdown fallback name extraction is case-sensitive, so `.MD`/mixed-case files keep the extension in the agent key.</violation>
</file>

<file name="src/plugin-config.ts">

<violation number="1" location="src/plugin-config.ts:258">
P2: Project `agent_definitions` are resolved against the workspace root instead of the project config directory, so relative paths from `.opencode` will point to the wrong location.</violation>
</file>

<file name="src/features/claude-code-agent-loader/opencode-config-agents-reader.ts">

<violation number="1" location="src/features/claude-code-agent-loader/opencode-config-agents-reader.ts:21">
P2: Hardcoded config directory paths bypass standard OPENCODE_CONFIG_DIR and XDG_CONFIG_HOME overrides.</violation>

<violation number="2" location="src/features/claude-code-agent-loader/opencode-config-agents-reader.ts:112">
P2: Global inline agents will incorrectly overwrite project-level agents with the same name.</violation>

<violation number="3" location="src/features/claude-code-agent-loader/opencode-config-agents-reader.ts:127">
P2: Agent precedence is inconsistent across config sources: inline agents use overwrite semantics, while `agent_definitions` only fill missing names, so collisions resolve differently depending on declaration style.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/features/claude-code-agent-loader/agent-definitions-loader.ts Outdated
Comment thread src/plugin-config.ts Outdated
Comment thread src/features/claude-code-agent-loader/opencode-config-agents-reader.ts Outdated
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

0 issues found across 1 file (changes from recent commits).

Requires human review: Auto-approval blocked by 2 unresolved issues from previous reviews.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 5 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/features/claude-code-agent-loader/opencode-config-agents-reader.ts">

<violation number="1" location="src/features/claude-code-agent-loader/opencode-config-agents-reader.ts:99">
P2: Using `in` on a plain object causes valid agent names that match inherited properties to be skipped.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/features/claude-code-agent-loader/opencode-config-agents-reader.ts Outdated
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/features/claude-code-agent-loader/opencode-config-agents-reader.ts">

<violation number="1" location="src/features/claude-code-agent-loader/opencode-config-agents-reader.ts:99">
P1: Use a null-prototype accumulator or explicitly reject `__proto__` here; `Object.hasOwn` allows a crafted agent name to mutate the result object's prototype. The same pattern appears again in the definition-agents merge below.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@brandonwebb-vista
Copy link
Copy Markdown
Contributor Author

@code-yeongyu can we get this merged? Or perhaps you can open up OmA to collaborators?

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

0 issues found across 1 file (changes from recent commits).

Requires human review: Large PR (2860 lines) fundamentally changing agent resolution and validation logic. Changes default tool restriction behavior. High complexity makes it impossible to guarantee 0% regressions.

@code-yeongyu
Copy link
Copy Markdown
Owner

[sisyphus-bot]

Hey @brandonwebb — first off, thank you so much for this PR! This is a really solid contribution. The dynamic agent resolution architecture is exactly the direction we wanted to go, and the test coverage (70+ new tests) is impressive. The precedence chain design is clean and the backward compatibility story is well thought out.

We ran two independent code reviews (security-focused + architecture-focused) and here is the consolidated feedback:


Blocking Issues (must fix before merge)

B1: Tool restriction bypass for custom agents (CRITICAL)

File: src/shared/agent-tool-restrictions.ts:47-50

The fallback to EXPLORATION_AGENT_DENYLIST means every custom agent is silently sandboxed to read-only mode — even if the user defines tools: "Bash,Read,Write,Edit" in their .md file. The tools config from agent definitions is never consulted at prompt time. Custom agents should either:

  • Return {} (empty restrictions) when no matching agent is found in AGENT_RESTRICTIONS, letting the agent definition's tools config drive permissions, OR
  • Explicitly document this as intentional sandboxing behavior

B2: Prototype pollution inconsistency

File: agent-definitions-loader.ts:70 (and loader.ts:35,47,59,71)

opencode-config-agents-reader.ts:82 correctly uses Object.create(null) to prevent prototype pollution, but the other 4 accumulator sites use plain {}. A file named __proto__.md or frontmatter name: __proto__ could hit the Object prototype. Apply Object.create(null) consistently across all agent-loading result objects.

B3: Custom agent bash access undocumented

File: agent-tool-restrictions.ts:50

The EXPLORATION_AGENT_DENYLIST denies write, edit, task, call_omo_agent — but not bash. Project-scoped custom agents (from .opencode/agents/ in a cloned repo) get shell execution. This may be intentional (matching Claude Code's trust model), but it must be explicitly documented or gated.

B4: Two test failures in new code

File: opencode-config-agents-reader.test.ts

"returns empty record when no opencode.json exists" and "handles malformed opencode.json gracefully" — the function scans the global config dir too, which picks up real agents on the test machine. Mock getOpenCodeConfigDir to point at a temp dir.


Non-blocking Issues (nice to have)

N1: Path containment gap in opencode-config-agents-reader

readOpencodeConfigAgents reads agent_definitions paths with no containment check, unlike plugin-config.ts which uses resolveAgentDefinitionPaths with project boundary enforcement. A project's opencode.json could reference ../../other-project/agent.md.

N2: No caching in resolveCallableAgents

Called per tool invocation with no caching. A session-scoped cache would eliminate redundant SDK IPC calls.

N3: parseToolsConfig duplicated 4x

Four near-identical implementations across loaders. Extract a single shared implementation into shared/.

N4: agent_definitions path schema too permissive

AgentDefinitionPathSchema = z.string() — no min length or pattern validation. Empty strings resolve to cwd via path.resolve(baseDir, "").

N5: No test coverage for resolveAgentDefinitionPaths

Security-sensitive function (path traversal boundary) with zero tests. Edge cases: symlink escape, .. traversal, absolute paths outside containment.

N6: Mode type assertion without validation

opencode-config-agents-reader.ts:68mode: (agent.mode as "subagent" | "primary" | "all") casts arbitrary user JSON strings without validation.


Verdict

Architecture: Strong. The client.app.agents() → merge with ALLOWED_AGENTS design scales well. The 4-tier loader precedence (opencode-project > opencode-global > user > project > plugin) is correct. Config schema changes are backward compatible.

Test coverage: Strong. 70+ new tests with good given/when/then structure.

Recommendation: Approve with required changes on B1–B4. The non-blocking items can be addressed in follow-up PRs.

Again, really appreciate this contribution — the custom agents feature has been one of the most requested items. Looking forward to landing this! 🎉

code-yeongyu added a commit to brandonwebb-vista/oh-my-opencode that referenced this pull request Apr 15, 2026
Blocking fixes:
- B1: Return empty restrictions for unknown/custom agents instead of
  EXPLORATION_AGENT_DENYLIST, allowing custom agents full tool access
- B2: Use Object.create(null) consistently across all 5 agent-loading
  result objects to prevent prototype pollution
- B3: Add code comment documenting custom agent bash access trust model
- B4: Mock getOpenCodeConfigDir in opencode-config-agents-reader tests
  to prevent global config dir leakage

Non-blocking fixes:
- N1: Use resolveAgentDefinitionPaths with project boundary enforcement
  in opencode-config-agents-reader for path containment
- N2: Add session-scoped 30s TTL cache to resolveCallableAgents to
  avoid redundant SDK IPC calls per tool invocation
- N3: Extract shared parseToolsConfig into src/shared/parse-tools-config.ts
  replacing 4 duplicated local implementations
- N4: Add .min(1) to AgentDefinitionPathSchema rejecting empty paths
- N5: Add resolve-agent-definition-paths.test.ts covering tilde expansion,
  relative paths, boundary enforcement, and null containmentDir
- N6: Validate agent mode against allowed values instead of bare type
  assertion in opencode-config-agents-reader
@code-yeongyu
Copy link
Copy Markdown
Owner

[sisyphus-bot]

I went ahead and pushed fixes for all the review findings directly to this branch. Here's what was addressed:

Blocking fixes:

  • B1 (agent-tool-restrictions.ts): Changed fallback from EXPLORATION_AGENT_DENYLIST to {} — custom agents now get full tool access by default, matching Claude Code's trust model
  • B2 (loader.ts, agent-definitions-loader.ts): Applied Object.create(null) consistently across all 6 accumulator sites
  • B3 (agent-tool-restrictions.ts): Added documentation comment explaining bash access for custom agents
  • B4 (opencode-config-agents-reader.test.ts): Fixed 2 test failures by mocking getOpenCodeConfigDir to prevent global config leakage

Non-blocking fixes:

  • N1 (opencode-config-agents-reader.ts): Added resolveAgentDefinitionPaths containment check for agent_definitions paths
  • N2 (agent-resolver.ts): Added session-scoped WeakMap cache to resolveCallableAgents
  • N3: Extracted shared parseToolsConfig into src/shared/parse-tools-config.ts, removed 4 duplicate implementations
  • N4 (agent-definitions.ts): Added .min(1) validation to AgentDefinitionPathSchema
  • N5: Added resolve-agent-definition-paths.test.ts with 122 lines covering ~/ expansion, relative paths, boundary enforcement, and null containmentDir
  • N6 (opencode-config-agents-reader.ts): Added runtime validation for mode against allowed values

Verification:

  • bun run typecheck: clean
  • PR-related tests (136): all pass, 0 fail
  • Full suite: 5530 pass, 11 fail (3 pre-existing, 8 flaky — all pass in isolation, confirmed test-order dependency unrelated to this PR)

The commit is dd92ee0d. Force-pushed to the branch.

brandonwebb-vista and others added 11 commits April 15, 2026 10:56
…t-based tests

- Fix agent-resolver.ts: add defensive validation on agent name (typeof, trim, filter)
- Fix tools.test.ts: correct mock to return {data: agents} matching SDK contract
- Fix agent-config-handler.ts: include opencode global/project agents in customAgentSummaries
- Add agent-resolver.test.ts: 14 requirement-based tests covering R1-R7 behavioral specs
- Add tools-edge-cases.test.ts: 5 integration tests for rollback, whitespace, dedup, session_id
…d JSON agent loader

Wave 1 of agent definitions enhancement (PR code-yeongyu#2299):

Schema & Configuration:
- Add agent_definitions field to oh-my-opencode config schema
- Support list of file paths to .md or .json agent definition files
- Add to PARTIAL_STRING_ARRAY_KEYS for Set-union merge semantics
- Implement eager path resolution in loadPluginConfig() before merging

Path Resolution:
- Create resolve-agent-definition-paths.ts helper
- User-level paths resolve from ~/.config/opencode/ (no containment)
- Project-level paths resolve from project root (with containment check)
- Homedir expansion, absolute/relative path handling

JSON Agent Loader:
- Create parseJsonAgentFile() for .json/.jsonc agent definitions
- Validate required fields (name, prompt)
- Support tools as string (comma-separated) or array
- Map model via mapClaudeModelToOpenCode()
- Comprehensive test suite (7 test cases, all passing)

Type Extensions:
- Extend AgentScope: add 'definition-file' and 'opencode-config'
- Add AgentJsonDefinition interface for JSON agent schema

All automated checks passing:
- lsp_diagnostics clean on all changed files
- json-agent-loader.test.ts: 7/7 passing
- Full typecheck: zero new errors
- QA evidence saved to .sisyphus/evidence/
- Add loadAgentDefinitions() for explicit file path loading (.md/.json/.jsonc)
- Add readOpencodeConfigAgents() for independent opencode.json(c) reading
- Extract parseMarkdownAgentFile() from loader.ts for reuse
- Refactor loader.ts to use extracted parser (-50 LOC)
- Add comprehensive test coverage (13 tests for definitions loader, 10 tests for opencode reader)
- Support inline agents + agent_definitions paths in opencode.json(c)
- Inline agents override definition-file agents (correct precedence)

Part of agent definitions enhancement (Wave 2/3)
…ecedence chain

- Modified agent-config-handler.ts to load and integrate both new agent sources
- Added loadAgentDefinitions() and readOpencodeConfigAgents() calls in loading phase
- Integrated both sources into agent precedence chains (both Sisyphus-enabled and disabled paths)
- Added detailed logging for new agent sources
- Added filtering logic to respect disabled_agents configuration
- Extended agent-config-handler.test.ts with 7 new integration tests
- All tests passing (18/18 integration, 65/65 loader suite)

Wave 3 of agent definitions enhancement complete.
…ckCtx factory

Dev added 4 tests (code-yeongyu#2852, model variant, category-derived overrides) that
referenced the old mockCtx constant. Our branch replaced it with a
createMockCtx() factory. Replace all 4 references.
- Case-insensitive .md extension stripping for agent name extraction
- Resolve project agent_definitions paths relative to config dir (.opencode/)
- Use getOpenCodeConfigDir() to respect OPENCODE_CONFIG_DIR/XDG_CONFIG_HOME
- First-write-wins semantics for both inline and definition-file agents
  so project-level agents always take precedence over global-level
…perty checks

Addresses cubic-dev-ai review: using 'in' on plain objects can skip valid
agent names that match inherited properties (toString, constructor, etc.).
Switched both occurrences in opencode-config-agents-reader.ts to
Object.hasOwn() for safe own-property checks.
…ution

Addresses cubic-dev-ai P1 review: Object.hasOwn() alone doesn't prevent
a crafted '__proto__' agent name from mutating the result object's
prototype chain. Using Object.create(null) eliminates inherited properties
entirely, making both the hasOwn checks and property assignments safe.
Blocking fixes:
- B1: Return empty restrictions for unknown/custom agents instead of
  EXPLORATION_AGENT_DENYLIST, allowing custom agents full tool access
- B2: Use Object.create(null) consistently across all 5 agent-loading
  result objects to prevent prototype pollution
- B3: Add code comment documenting custom agent bash access trust model
- B4: Mock getOpenCodeConfigDir in opencode-config-agents-reader tests
  to prevent global config dir leakage

Non-blocking fixes:
- N1: Use resolveAgentDefinitionPaths with project boundary enforcement
  in opencode-config-agents-reader for path containment
- N2: Add session-scoped 30s TTL cache to resolveCallableAgents to
  avoid redundant SDK IPC calls per tool invocation
- N3: Extract shared parseToolsConfig into src/shared/parse-tools-config.ts
  replacing 4 duplicated local implementations
- N4: Add .min(1) to AgentDefinitionPathSchema rejecting empty paths
- N5: Add resolve-agent-definition-paths.test.ts covering tilde expansion,
  relative paths, boundary enforcement, and null containmentDir
- N6: Validate agent mode against allowed values instead of bare type
  assertion in opencode-config-agents-reader
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 15 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/tools/call-omo-agent/agent-resolver.ts">

<violation number="1" location="src/tools/call-omo-agent/agent-resolver.ts:54">
P2: The cache uses a global `"__default__"` key when `sessionId` is missing, so agent lists can bleed across contexts and validate against the wrong dynamic agents for up to 30 seconds.</violation>
</file>

<file name="src/features/claude-code-agent-loader/opencode-config-agents-reader.ts">

<violation number="1" location="src/features/claude-code-agent-loader/opencode-config-agents-reader.ts:95">
P2: Global OpenCode config `agent_definitions` are incorrectly blocked by project-boundary enforcement.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/tools/call-omo-agent/agent-resolver.ts
The opencode-config-agents-reader.test.ts was using mock.module() which
permanently replaced the module in bun's module cache, causing state
pollution in downstream tests (plugin-detection, write-omo-config,
config-loader). Replaced with spyOn() pattern that properly restores
in afterEach.
@code-yeongyu code-yeongyu force-pushed the feat/dynamic-custom-agent-support branch from dd92ee0 to ab11f2e Compare April 15, 2026 02:17
@code-yeongyu
Copy link
Copy Markdown
Owner

[sisyphus-bot]

Pushed additional fix: replaced mock.module() with spyOn() in opencode-config-agents-reader.test.ts to prevent test pollution.

Root cause: mock.module() permanently replaced modules in bun's module cache, causing state leakage into plugin-detection.test.ts, write-omo-config.test.ts, and config-loader.test.ts.

Result:

  • bun run typecheck: clean
  • bun test: 5548 pass, 0 fail (was 11 failures before)
  • Rebased onto latest dev

Branch is ready for merge.

@code-yeongyu code-yeongyu merged commit 0217189 into code-yeongyu:dev Apr 15, 2026
2 checks passed
@brandonwebb-vista brandonwebb-vista deleted the feat/dynamic-custom-agent-support branch April 15, 2026 02:24
github-actions bot pushed a commit to Vacbo/oh-my-opencode that referenced this pull request Apr 17, 2026
…d JSON agent loader

Wave 1 of agent definitions enhancement (PR code-yeongyu#2299):

Schema & Configuration:
- Add agent_definitions field to oh-my-opencode config schema
- Support list of file paths to .md or .json agent definition files
- Add to PARTIAL_STRING_ARRAY_KEYS for Set-union merge semantics
- Implement eager path resolution in loadPluginConfig() before merging

Path Resolution:
- Create resolve-agent-definition-paths.ts helper
- User-level paths resolve from ~/.config/opencode/ (no containment)
- Project-level paths resolve from project root (with containment check)
- Homedir expansion, absolute/relative path handling

JSON Agent Loader:
- Create parseJsonAgentFile() for .json/.jsonc agent definitions
- Validate required fields (name, prompt)
- Support tools as string (comma-separated) or array
- Map model via mapClaudeModelToOpenCode()
- Comprehensive test suite (7 test cases, all passing)

Type Extensions:
- Extend AgentScope: add 'definition-file' and 'opencode-config'
- Add AgentJsonDefinition interface for JSON agent schema

All automated checks passing:
- lsp_diagnostics clean on all changed files
- json-agent-loader.test.ts: 7/7 passing
- Full typecheck: zero new errors
- QA evidence saved to .sisyphus/evidence/
github-actions bot pushed a commit to Vacbo/oh-my-opencode that referenced this pull request Apr 17, 2026
Blocking fixes:
- B1: Return empty restrictions for unknown/custom agents instead of
  EXPLORATION_AGENT_DENYLIST, allowing custom agents full tool access
- B2: Use Object.create(null) consistently across all 5 agent-loading
  result objects to prevent prototype pollution
- B3: Add code comment documenting custom agent bash access trust model
- B4: Mock getOpenCodeConfigDir in opencode-config-agents-reader tests
  to prevent global config dir leakage

Non-blocking fixes:
- N1: Use resolveAgentDefinitionPaths with project boundary enforcement
  in opencode-config-agents-reader for path containment
- N2: Add session-scoped 30s TTL cache to resolveCallableAgents to
  avoid redundant SDK IPC calls per tool invocation
- N3: Extract shared parseToolsConfig into src/shared/parse-tools-config.ts
  replacing 4 duplicated local implementations
- N4: Add .min(1) to AgentDefinitionPathSchema rejecting empty paths
- N5: Add resolve-agent-definition-paths.test.ts covering tilde expansion,
  relative paths, boundary enforcement, and null containmentDir
- N6: Validate agent mode against allowed values instead of bare type
  assertion in opencode-config-agents-reader
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

triage:feature-request Feature or enhancement request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: [Feature Request] Support custom agents in call_omo_agent tool via ALLOWED_AGENTS whitelist

5 participants