Skip to content

feat: add /agent-promote command for hot-reloading agent modes#2

Merged
fdematos merged 8 commits intomainfrom
feat/agent-promote
Jan 13, 2026
Merged

feat: add /agent-promote command for hot-reloading agent modes#2
fdematos merged 8 commits intomainfrom
feat/agent-promote

Conversation

@fdematos
Copy link
Contributor

Summary

Add new /agent-promote command to change plugin agent types at runtime without restarting OpenCode.

Changes

  • New tool agent-promote in src/tools/agent-promote.ts
  • New command command/agent-promote.md
  • Hook config integration to apply agent mode overrides
  • Documentation in README.md

Usage

/agent-promote rubber-duck primary   # Make agent available in Tab selection
/agent-promote architect all         # Make agent available everywhere
/agent-promote code-reviewer subagent # Revert to subagent only

How it works

  1. Tool stores promoted agent in memory Map
  2. Shows toast notification
  3. Calls instance.dispose() to reload config
  4. Hook config reads Map and overrides agent mode

fdematos and others added 6 commits January 13, 2026 23:24
Add new command to change plugin agent types (subagent/primary/all)
at runtime without restarting OpenCode. Uses tui.showToast for
notification and instance.dispose for config reload.
Fixes duplicate Authorization header error when OpenCode action
tries to access the repository.
Co-authored-by: fdematos <fdematos@users.noreply.github.com>
@opencode-agent
Copy link
Contributor

Good, the build and tests pass. Now I have a complete picture. Let me provide my comprehensive review:


Pull Request Review: /agent-promote command

Summary

This PR adds a new /agent-promote command that allows changing plugin agent types at runtime. The implementation follows existing patterns and the code passes typecheck and all 116 tests.


Code Quality Issues

1. Missing test coverage (src/tools/agent-promote.ts)

The codebase has test files for blockchain tools, config-paths, code-files, and the main index. The new agent-promote tool has no tests.

Recommendation: Add src/tools/agent-promote.test.ts covering:

  • Valid grade values (subagent, primary, all)
  • Invalid grade values
  • Agent not found in plugin
  • Agent already at target grade
  • Successful promotion storing in Map
  • getPromotedAgents() returning correct data

2. Module-level state without cleanup (src/tools/agent-promote.ts:10)

const promotedAgents = new Map<string, AgentMode>()

The promotedAgents Map persists for the entire process lifetime. While the README documents "Changes persist in memory until OpenCode restarts", there's no way to reset this state programmatically. This could be intentional, but consider:

  • Adding a clearPromotedAgents() export for testing
  • Or documenting that this is intentional behavior

3. Type narrowing could be cleaner (src/tools/agent-promote.ts:31,47)

if (!VALID_GRADES.includes(grade as AgentMode)) { ... }
// ...
promotedAgents.set(name, grade as AgentMode)

Suggestion: Use a type guard for cleaner narrowing:

function isValidAgentMode(grade: string): grade is AgentMode {
  return VALID_GRADES.includes(grade as AgentMode)
}

Potential Bugs

1. No error handling around dispose() (src/tools/agent-promote.ts:58)

await client.instance.dispose()

If dispose() throws, the user sees the toast "Promoting..." but the promotion may not take effect. The tool would throw an unhandled error.

Recommendation:

try {
  await client.instance.dispose()
} catch (err) {
  await client.tui.showToast({
    body: {
      message: `Failed to reload config: ${String(err)}`,
      variant: "error",
      duration: 5000,
    },
  })
  throw err
}

2. Unhandled API errors (src/tools/agent-promote.ts:39-40)

const agentsResp = await client.app.agents()
const agents = agentsResp.data ?? []

If client.app.agents() throws (e.g., network error), this bubbles up with a potentially confusing error message.

Suggestion: Wrap in try/catch with user-friendly message.


Suggestions for Improvement

1. Hardcoded agent names in docs (command/agent-promote.md:13)

| `name` | Agent name from the plugin | `rubber-duck`, `architect`, `code-reviewer`, `code-simplifier`, `doc-writer`, `partner` |

These will go stale if agents are added/removed.

Suggestion: Either remove specific names or add a note that the list may change:

| `name` | Agent name from the plugin | See [Agents](#agents) section in README |

2. Consider semantic naming

The command uses grade parameter but conceptually it's setting the agent's mode. The term "promote" implies going up, but /agent-promote code-reviewer subagent is a demotion.

Suggestions:

  • Rename to /agent-mode for neutrality
  • Or add /agent-demote alias
  • Or just document this clearly (which the PR does)

3. Toast message timing

The toast shows "Promoting..." before dispose() completes, but the return message says the action is already done. Consider:

  • Showing toast after successful dispose(), or
  • Updating toast message to past tense: "Agent promoted to..."

What's Working Well

  1. Clean integration with config hook (src/index.ts:267-274) - The config hook properly applies promoted agents from the Map
  2. Good logging (src/tools/agent-promote.ts:48) - Uses existing logger pattern
  3. Proper exports (src/tools/index.ts:4) - Exports both the factory and getter
  4. Comprehensive README documentation (README.md:233-270) - Well-documented with examples

Overall Assessment

Area Status
Typecheck ✅ Passes
Tests ✅ 116/116 pass
Code style ✅ Follows patterns
Documentation ✅ Comprehensive
Test coverage ⚠️ Missing for new tool
Error handling ⚠️ Could be improved

Priority fixes:

  1. High - Add test file for agent-promote tool
  2. Medium - Add error handling around dispose() call
  3. Low - Consider removing hardcoded agent names from command docs

New%20session%20-%202026-01-13T22%3A40%3A37.380Z
opencode session  |  github run

@smartfrog smartfrog deleted a comment from opencode-agent bot Jan 13, 2026
@smartfrog smartfrog deleted a comment from opencode-agent bot Jan 13, 2026
@smartfrog smartfrog deleted a comment from opencode-agent bot Jan 13, 2026
@smartfrog smartfrog deleted a comment from opencode-agent bot Jan 13, 2026
Extract core logic to agent-promote-core.ts for testability.
Tests cover validation, grade types, and promotedAgents Map.
@opencode-agent
Copy link
Contributor

All 127 tests pass and typecheck succeeds. Now let me provide a comprehensive review.


Pull Request Review: /agent-promote command

Summary

This PR adds a well-implemented /agent-promote command for changing plugin agent types at runtime. The code follows existing patterns, includes good test coverage, and passes all 127 tests with clean typecheck.


Code Quality Assessment

Strengths:

  1. Good separation of concerns - Core logic extracted to agent-promote-core.ts (src/tools/agent-promote-core.ts:1-22) for testability, with tool implementation in agent-promote.ts

  2. Proper type guard (src/tools/agent-promote-core.ts:15-17):

    export function validateGrade(grade: string): grade is AgentMode {
      return VALID_GRADES.includes(grade as AgentMode)
    }
  3. Comprehensive test coverage - The test file covers all validation functions, Map operations, and edge cases (src/tools/agent-promote.test.ts:1-94)

  4. Clean integration (src/index.ts:270-274) - The config hook properly applies promoted agents

  5. Well-documented - README section is thorough with usage examples


Issues Found

Medium Priority

1. Missing error handling around dispose() (src/tools/agent-promote.ts:66)

await client.instance.dispose()

If dispose() throws, the user sees the success toast but the promotion may not take effect. Consider:

try {
  await client.instance.dispose()
} catch (err) {
  await client.tui.showToast({
    body: {
      message: `Failed to reload config: ${String(err)}`,
      variant: "error",
      duration: 5000,
    },
  })
  throw err
}

2. Unhandled API errors (src/tools/agent-promote.ts:47-48)

const agentsResp = await client.app.agents()
const agents = agentsResp.data ?? []

If client.app.agents() throws, it bubbles up with a potentially confusing error message. Consider wrapping with a try/catch.

Low Priority

3. Hardcoded agent names in docs (command/agent-promote.md:13)

| `name` | Agent name from the plugin | `rubber-duck`, `architect`, `code-reviewer`, `code-simplifier`, `doc-writer`, `partner` |

These will go stale if agents are added/removed. Consider:

| `name` | Agent name from the plugin | See [Agents](#agents) section in README |

4. Module-level state without test cleanup (src/tools/agent-promote-core.ts:5)

The promotedAgents Map persists across tests. While tests currently use unique agent names (test-agent-1, test-agent-2, etc.), consider adding a clearPromotedAgents() export for proper test isolation:

export function clearPromotedAgents(): void {
  promotedAgents.clear()
}

5. Toast message timing (src/tools/agent-promote.ts:58-64)

The toast shows "Promoting..." before dispose() completes, but success isn't guaranteed. Consider showing the toast after successful dispose() or changing to past tense.


Test Coverage Analysis

Tests cover:

  • ✅ Valid grade values (subagent, primary, all)
  • ✅ Invalid grade values
  • ✅ Agent name validation
  • ✅ Map operations (set, get, update)
  • ✅ Multiple agents
  • ✅ ReadonlyMap return type

Missing (but acceptable for this scope):

  • Integration tests for the full tool execution path
  • Mocked client interactions

Summary

Area Status
Typecheck ✅ Passes
Tests ✅ 127/127 pass
Code style ✅ Follows existing patterns
Test coverage ✅ Core logic covered
Documentation ✅ Comprehensive
Error handling ⚠️ Could be improved

Recommendation: Approve with minor suggestions. The implementation is solid and follows the codebase conventions. The error handling improvements are nice-to-have but not blocking.

New%20session%20-%202026-01-13T22%3A51%3A38.240Z
opencode session  |  github run

@opencode-agent
Copy link
Contributor

Now I have a complete picture. Let me provide my comprehensive review.


Pull Request Review: /agent-promote command

Summary

This PR adds a well-implemented /agent-promote command for changing plugin agent types at runtime. The code passes typecheck and all 127 tests.


Code Quality Assessment

Strengths:

  1. Good separation of concerns - Core logic extracted to agent-promote-core.ts for testability, with tool implementation in agent-promote.ts

  2. Proper type guard (src/tools/agent-promote-core.ts:15-17):

    export function validateGrade(grade: string): grade is AgentMode {
      return VALID_GRADES.includes(grade as AgentMode)
    }
  3. Comprehensive test coverage - Tests cover validation functions, Map operations, and edge cases (src/tools/agent-promote.test.ts)

  4. Clean integration (src/index.ts:270-274) - The config hook properly applies promoted agents from the Map

  5. Well-documented - README section is thorough with examples


Issues Found

Medium Priority

1. Missing error handling around dispose() (src/tools/agent-promote.ts:66)

await client.instance.dispose()

If dispose() throws, the user sees the success toast but the promotion may not take effect. Consider:

try {
  await client.instance.dispose()
} catch (err) {
  await client.tui.showToast({
    body: {
      message: `Failed to reload config: ${String(err)}`,
      variant: "error",
      duration: 5000,
    },
  })
  throw err
}

2. Unhandled API errors (src/tools/agent-promote.ts:47-48)

const agentsResp = await client.app.agents()
const agents = agentsResp.data ?? []

If client.app.agents() throws, it bubbles up with a potentially confusing error message. Consider wrapping with a try/catch.

Low Priority

3. Module-level state without test cleanup (src/tools/agent-promote-core.ts:5)

The promotedAgents Map persists across tests. Tests use unique agent names (test-agent-1, agent-a, etc.) to avoid collisions, but consider adding a clearPromotedAgents() export for proper test isolation:

export function clearPromotedAgents(): void {
  promotedAgents.clear()
}

4. Toast message timing (src/tools/agent-promote.ts:58-64)

The toast shows "Promoting..." before dispose() completes. Consider showing the toast after successful dispose() or using past tense: "Agent promoted to...".


Workflow Changes

The .github/workflows/opencode-review.yml changes are unrelated to the feature:

  • Permissions changed from read to write
  • API key changed from ANTHROPIC_API_KEY to OPENCODE_API_KEY
  • Model changed to opencode/claude-opus-4-5
  • Added persist-credentials: false

These should ideally be in a separate PR for clarity.


Test Results

Area Status
Typecheck ✅ Passes
Tests ✅ 127/127 pass
Code style ✅ Follows existing patterns
Test coverage ✅ Core logic covered
Documentation ✅ Comprehensive
Error handling ⚠️ Could be improved

Recommendation

Approve with minor suggestions. The implementation is solid and follows the codebase conventions. The error handling improvements are nice-to-have but not blocking. Consider separating the workflow changes into a separate PR for cleaner history.

New%20session%20-%202026-01-13T22%3A56%3A47.331Z
opencode session  |  github run

@fdematos fdematos merged commit 4df1f9f into main Jan 13, 2026
2 checks passed
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

Comments