opencode-conductor-cdd-plugin 1.0.0-beta.15 → 1.0.0-beta.16

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.
package/README.md CHANGED
@@ -19,7 +19,7 @@ The philosophy is simple: **control your code by controlling your context.** By
19
19
  * **Smart Revert**: A Git-aware revert system that understands logical units of work (Tracks, Phases, Tasks) instead of just raw commit hashes.
20
20
  * **19+ Style Templates**: Built-in support for a vast range of languages including Rust, Solidity, Zig, Julia, Kotlin, Swift, and more.
21
21
  * **Zero-Config Bootstrap**: Automatically installs agents and commands to your global OpenCode configuration on first run.
22
- * **Sisyphus Synergy**: Optimized to work alongside [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode) for a multi-agent team experience.
22
+ * **Sisyphus Synergy**: Optimized to work alongside [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode) and [oh-my-opencode-slim](https://github.com/code-yeongyu/oh-my-opencode-slim) for a multi-agent team experience.
23
23
  * **Agent Agnostic**: Commands can be invoked by any agent, giving you the freedom to choose your primary interface.
24
24
 
25
25
  ---
@@ -90,6 +90,32 @@ We highly recommend pinning the `@cdd` agent to a "flash" model for optimal perf
90
90
  }
91
91
  ```
92
92
 
93
+ ### oh-my-opencode-slim Config
94
+ **File:** `~/.config/opencode/oh-my-opencode-slim.json`
95
+ ```json
96
+ {
97
+ "agents": {
98
+ "cdd": {
99
+ "model": "anthropic/claude-3-5-sonnet"
100
+ }
101
+ }
102
+ }
103
+ ```
104
+
105
+ **Framework Detection Priority:**
106
+ When multiple synergy frameworks are detected, Conductor CDD prioritizes oh-my-opencode-slim over oh-my-opencode. If neither framework config is found, Conductor operates in manual mode.
107
+
108
+ **Agent Mappings for oh-my-opencode-slim:**
109
+ Conductor CDD automatically maps generic agent names to slim-specific agent names:
110
+ - `explore` → `explorer` (codebase exploration)
111
+ - `frontend-ui-ux-engineer` → `designer` (UI/UX tasks)
112
+ - `document-writer` → `librarian` (documentation)
113
+ - `oracle` → `oracle` (knowledge queries)
114
+ - `sisyphus` → Falls back to `@cdd` (no slim equivalent)
115
+
116
+ **Checking Synergy Status:**
117
+ Use `/cdd:status` to see which synergy framework is active and which agents are available.
118
+
93
119
  ---
94
120
 
95
121
  ## 📋 Commands Reference
@@ -104,11 +130,24 @@ We highly recommend pinning the `@cdd` agent to a "flash" model for optimal perf
104
130
 
105
131
  ---
106
132
 
107
- ## 🤝 Synergy with OhMyOpenCode
133
+ ## 🤝 Synergy with OhMyOpenCode & oh-my-opencode-slim
134
+
135
+ Conductor CDD works seamlessly with both **OhMyOpenCode** and **oh-my-opencode-slim** frameworks.
136
+
137
+ ### OhMyOpenCode Integration
138
+ When using **OhMyOpenCode**, `@cdd` acts as your Technical Lead. While **Sisyphus** manages the general conversation and orchestration, he can delegate complex architectural planning and protocol enforcement to the `@cdd` agent.
139
+
140
+ ### oh-my-opencode-slim Integration
141
+ When using **oh-my-opencode-slim**, `@cdd` integrates with the lightweight agent framework, automatically mapping to slim-specific agents like `explorer`, `designer`, and `librarian` for specialized tasks.
108
142
 
109
- If you use the **OhMyOpenCode** suite, `@cdd` acts as your Technical Lead. While **Sisyphus** manages the general conversation and orchestration, he can delegate complex architectural planning and protocol enforcement to the `@cdd` agent.
143
+ ### Key Features
144
+ - **Automatic Detection**: Conductor CDD automatically detects which framework you're using (if any)
145
+ - **Priority Resolution**: When both frameworks are present, oh-my-opencode-slim takes priority
146
+ - **Agent Mapping**: Generic agent names are automatically mapped to framework-specific names
147
+ - **Graceful Fallback**: If a requested agent is unavailable or disabled, tasks fall back to `@cdd`
148
+ - **Loop Protection**: Built-in safeguards ensure no conflicts with framework continuation enforcers during interactive Q&A
110
149
 
111
- Conductor CDD includes built-in "Loop Protection" to ensure it never conflicts with OhMyOpenCode's continuation enforcers during interactive Q&A sessions.
150
+ Use `/cdd:status` to see which synergy framework is active and which agents are available.
112
151
 
113
152
  ---
114
153
 
@@ -27,13 +27,17 @@ Your mission is to ensure that software development follows a rigorous, context-
27
27
  - **Model Selection**: You prefer "flash" models for efficiency and speed during planning and tool orchestration.
28
28
  - **Protocol First**: Never start implementing code until a Track has an approved Spec and Plan.
29
29
  - **Collaboration**: You work alongside the user. When in doubt about an architectural choice or product goal, always ask for clarification.
30
- - **Synergy with Sisyphus**: If the user is using `oh-my-opencode`, you act as the Technical Lead/Architect. You can delegate tasks to specialized agents using the `orchestrator_delegate` tool:
31
- - `@frontend-ui-ux-engineer`: For UI/UX implementation and frontend components.
32
- - `@document-writer`: For documentation, READMEs, and technical guides.
33
- - `@sisyphus`: For general implementation, coordination, and bug fixing.
34
- - `@oracle`: For architectural decisions, deep code analysis, and expert reviews.
35
- - `@librarian`: For external library research, documentation retrieval, and finding usage examples.
36
- - `@explore`: For codebase search, understanding unfamiliar code, and finding logic.
30
+ - **Synergy with OhMyOpenCode & oh-my-opencode-slim**: You integrate seamlessly with both synergy frameworks, acting as Technical Lead/Architect. You can delegate tasks to specialized agents using the `orchestrator_delegate` tool:
31
+ - **Generic Agent Names** (automatically mapped to framework-specific names):
32
+ - `@explore`: For codebase search, understanding unfamiliar code, and finding logic. Maps to `explorer` in oh-my-opencode-slim.
33
+ - `@frontend-ui-ux-engineer`: For UI/UX implementation and frontend components. Maps to `designer` in oh-my-opencode-slim.
34
+ - `@document-writer`: For documentation, READMEs, and technical guides. Maps to `librarian` in oh-my-opencode-slim.
35
+ - `@oracle`: For architectural decisions, deep code analysis, and expert reviews (available in both frameworks).
36
+ - `@sisyphus`: For general implementation, coordination, and bug fixing (oh-my-opencode only; falls back to `@cdd` in slim).
37
+ - **Framework Detection**: The system automatically detects which synergy framework is active (if any) and maps agent names appropriately.
38
+ - **Graceful Fallback**: If a requested agent is unavailable or disabled in the active framework, tasks automatically fall back to `@cdd`.
39
+ - **Priority Resolution**: When both frameworks are configured, oh-my-opencode-slim takes priority.
40
+ - **Status Visibility**: Use `orchestrator_status` to check which synergy framework is active and which agents are available.
37
41
 
38
42
  ## Loop Protection Directive (CRITICAL)
39
43
  If you see a "[SYSTEM REMINDER - TODO CONTINUATION]" or "Continue" prompt from an enforcer while you are waiting for user input, you MUST ignore it. Respond with: "I am currently in an interactive Orchestrator phase. Awaiting user response."
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,246 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { resolveAgentName, isAgentAvailable, getAgentMapping } from '../../utils/agentMapping.js';
3
+ import { shouldDelegateToSynergy, getDelegationStrategy, resolveAgentForDelegation } from '../../utils/synergyDelegation.js';
4
+ /**
5
+ * Integration tests for oh-my-opencode-slim synergy support
6
+ *
7
+ * These tests verify that the various modules work together correctly
8
+ * for oh-my-opencode-slim integration, focusing on agent mapping, delegation,
9
+ * and the interaction between components.
10
+ */
11
+ describe('oh-my-opencode-slim Integration Tests', () => {
12
+ describe('Complete Workflow: Mapping → Delegation', () => {
13
+ it('should complete full agent resolution workflow for slim framework', () => {
14
+ const framework = 'oh-my-opencode-slim';
15
+ const availableAgents = ['explorer', 'designer', 'librarian', 'oracle'];
16
+ // Step 1: Agent Mapping - Test that generic names map to slim-specific names
17
+ const explorerMapping = resolveAgentName('explore', framework);
18
+ expect(explorerMapping).toBe('explorer');
19
+ const designerMapping = resolveAgentName('frontend-ui-ux-engineer', framework);
20
+ expect(designerMapping).toBe('designer');
21
+ const librarianMapping = resolveAgentName('document-writer', framework);
22
+ expect(librarianMapping).toBe('librarian');
23
+ const oracleMapping = resolveAgentName('oracle', framework);
24
+ expect(oracleMapping).toBe('oracle');
25
+ // sisyphus has no slim equivalent
26
+ const sisyphusMapping = resolveAgentName('sisyphus', framework);
27
+ expect(sisyphusMapping).toBe(null);
28
+ // Step 2: Agent Availability - Test that mapped names are available
29
+ expect(isAgentAvailable(explorerMapping, availableAgents)).toBe(true);
30
+ expect(isAgentAvailable(designerMapping, availableAgents)).toBe(true);
31
+ expect(isAgentAvailable(librarianMapping, availableAgents)).toBe(true);
32
+ expect(isAgentAvailable(oracleMapping, availableAgents)).toBe(true);
33
+ expect(isAgentAvailable(sisyphusMapping, availableAgents)).toBe(false);
34
+ // Step 3: Delegation Strategy - Test that delegation is enabled for slim
35
+ expect(shouldDelegateToSynergy(framework)).toBe(true);
36
+ expect(getDelegationStrategy(framework)).toBe('delegate');
37
+ // Step 4: Complete Delegation Resolution - Test end-to-end resolution
38
+ const exploreResult = resolveAgentForDelegation('explore', framework, availableAgents);
39
+ expect(exploreResult.success).toBe(true);
40
+ expect(exploreResult.resolvedAgent).toBe('explorer');
41
+ expect(exploreResult.shouldFallback).toBe(false);
42
+ expect(exploreResult.reason).toBeUndefined();
43
+ const uiResult = resolveAgentForDelegation('frontend-ui-ux-engineer', framework, availableAgents);
44
+ expect(uiResult.success).toBe(true);
45
+ expect(uiResult.resolvedAgent).toBe('designer');
46
+ expect(uiResult.shouldFallback).toBe(false);
47
+ const docResult = resolveAgentForDelegation('document-writer', framework, availableAgents);
48
+ expect(docResult.success).toBe(true);
49
+ expect(docResult.resolvedAgent).toBe('librarian');
50
+ expect(docResult.shouldFallback).toBe(false);
51
+ const oracleResult = resolveAgentForDelegation('oracle', framework, availableAgents);
52
+ expect(oracleResult.success).toBe(true);
53
+ expect(oracleResult.resolvedAgent).toBe('oracle');
54
+ expect(oracleResult.shouldFallback).toBe(false);
55
+ // sisyphus should fallback since it has no slim equivalent
56
+ const sisyphusResult = resolveAgentForDelegation('sisyphus', framework, availableAgents);
57
+ expect(sisyphusResult.success).toBe(false);
58
+ expect(sisyphusResult.resolvedAgent).toBe(null);
59
+ expect(sisyphusResult.shouldFallback).toBe(true);
60
+ expect(sisyphusResult.reason).toContain('not available');
61
+ expect(sisyphusResult.reason).toContain('sisyphus');
62
+ });
63
+ it('should handle unavailable agents gracefully', () => {
64
+ const framework = 'oh-my-opencode-slim';
65
+ // Only explorer is available
66
+ const availableAgents = ['explorer'];
67
+ // explore maps to explorer and is available
68
+ const exploreResult = resolveAgentForDelegation('explore', framework, availableAgents);
69
+ expect(exploreResult.success).toBe(true);
70
+ expect(exploreResult.resolvedAgent).toBe('explorer');
71
+ // frontend-ui-ux-engineer maps to designer but designer is not available
72
+ const uiResult = resolveAgentForDelegation('frontend-ui-ux-engineer', framework, availableAgents);
73
+ expect(uiResult.success).toBe(false);
74
+ expect(uiResult.shouldFallback).toBe(true);
75
+ expect(uiResult.reason).toContain('not available');
76
+ // document-writer maps to librarian but librarian is not available
77
+ const docResult = resolveAgentForDelegation('document-writer', framework, availableAgents);
78
+ expect(docResult.success).toBe(false);
79
+ expect(docResult.shouldFallback).toBe(true);
80
+ expect(docResult.reason).toContain('not available');
81
+ });
82
+ it('should work with empty available agents list', () => {
83
+ const framework = 'oh-my-opencode-slim';
84
+ const availableAgents = [];
85
+ // All agents should fallback when none are available
86
+ const exploreResult = resolveAgentForDelegation('explore', framework, availableAgents);
87
+ expect(exploreResult.success).toBe(false);
88
+ expect(exploreResult.shouldFallback).toBe(true);
89
+ expect(exploreResult.reason).toContain('not available');
90
+ const sisyphusResult = resolveAgentForDelegation('sisyphus', framework, availableAgents);
91
+ expect(sisyphusResult.success).toBe(false);
92
+ expect(sisyphusResult.shouldFallback).toBe(true);
93
+ expect(sisyphusResult.reason).toContain('not available');
94
+ });
95
+ });
96
+ describe('Framework Priority and Switching', () => {
97
+ it('should handle oh-my-opencode framework (pass-through behavior)', () => {
98
+ const framework = 'oh-my-opencode';
99
+ const availableAgents = ['sisyphus', 'explore', 'oracle'];
100
+ // oh-my-opencode uses pass-through mapping (no transformation)
101
+ expect(resolveAgentName('explore', framework)).toBe('explore');
102
+ expect(resolveAgentName('sisyphus', framework)).toBe('sisyphus');
103
+ expect(resolveAgentName('oracle', framework)).toBe('oracle');
104
+ expect(resolveAgentName('frontend-ui-ux-engineer', framework)).toBe('frontend-ui-ux-engineer');
105
+ // Delegation should be enabled
106
+ expect(shouldDelegateToSynergy(framework)).toBe(true);
107
+ expect(getDelegationStrategy(framework)).toBe('delegate');
108
+ // Since oh-my-opencode doesn't have agent list detection,
109
+ // delegation with empty list means agent is assumed unavailable
110
+ const exploreResult = resolveAgentForDelegation('explore', framework, []);
111
+ expect(exploreResult.success).toBe(false);
112
+ expect(exploreResult.shouldFallback).toBe(true);
113
+ expect(exploreResult.reason).toContain('disabled or not available');
114
+ });
115
+ it('should handle manual mode (no synergy framework)', () => {
116
+ const framework = 'none';
117
+ const availableAgents = [];
118
+ // Manual mode means no delegation
119
+ expect(shouldDelegateToSynergy(framework)).toBe(false);
120
+ expect(getDelegationStrategy(framework)).toBe('manual');
121
+ // All agents should fallback in manual mode
122
+ const exploreResult = resolveAgentForDelegation('explore', framework, availableAgents);
123
+ expect(exploreResult.success).toBe(false);
124
+ expect(exploreResult.shouldFallback).toBe(true);
125
+ expect(exploreResult.reason).toContain('No synergy framework active');
126
+ const sisyphusResult = resolveAgentForDelegation('sisyphus', framework, availableAgents);
127
+ expect(sisyphusResult.success).toBe(false);
128
+ expect(sisyphusResult.shouldFallback).toBe(true);
129
+ expect(sisyphusResult.reason).toContain('No synergy framework active');
130
+ });
131
+ });
132
+ describe('Agent Mapping Completeness', () => {
133
+ it('should have mappings for all known generic agent names', () => {
134
+ const framework = 'oh-my-opencode-slim';
135
+ // Test all known mappings
136
+ const mappings = [
137
+ { generic: 'explore', slim: 'explorer' },
138
+ { generic: 'frontend-ui-ux-engineer', slim: 'designer' },
139
+ { generic: 'document-writer', slim: 'librarian' },
140
+ { generic: 'oracle', slim: 'oracle' }, // pass-through
141
+ { generic: 'sisyphus', slim: null }, // no equivalent
142
+ ];
143
+ for (const { generic, slim } of mappings) {
144
+ const resolved = resolveAgentName(generic, framework);
145
+ expect(resolved).toBe(slim);
146
+ }
147
+ });
148
+ it('should get complete agent mapping as object', () => {
149
+ const framework = 'oh-my-opencode-slim';
150
+ const mapping = getAgentMapping(framework);
151
+ // Check that known mappings exist
152
+ expect(mapping['explore']).toBe('explorer');
153
+ expect(mapping['frontend-ui-ux-engineer']).toBe('designer');
154
+ expect(mapping['document-writer']).toBe('librarian');
155
+ expect(mapping['oracle']).toBe('oracle');
156
+ expect(mapping['sisyphus']).toBe(null);
157
+ });
158
+ it('should have empty mapping for oh-my-opencode (pass-through)', () => {
159
+ const framework = 'oh-my-opencode';
160
+ const mapping = getAgentMapping(framework);
161
+ // oh-my-opencode has no special mappings
162
+ expect(mapping).toEqual({});
163
+ });
164
+ it('should pass through unknown agent names for oh-my-opencode only', () => {
165
+ // oh-my-opencode passes through unknown agents
166
+ expect(resolveAgentName('unknown-agent', 'oh-my-opencode')).toBe('unknown-agent');
167
+ expect(resolveAgentName('custom-agent', 'oh-my-opencode')).toBe('custom-agent');
168
+ expect(resolveAgentName('test-agent', 'oh-my-opencode')).toBe('test-agent');
169
+ // oh-my-opencode-slim returns null for unknown agents
170
+ expect(resolveAgentName('unknown-agent', 'oh-my-opencode-slim')).toBe(null);
171
+ expect(resolveAgentName('custom-agent', 'oh-my-opencode-slim')).toBe(null);
172
+ });
173
+ });
174
+ describe('Backward Compatibility', () => {
175
+ it('should not break oh-my-opencode agent resolution', () => {
176
+ const framework = 'oh-my-opencode';
177
+ // All agent names should pass through unchanged for oh-my-opencode
178
+ const agents = ['sisyphus', 'explore', 'oracle', 'frontend-ui-ux-engineer', 'document-writer'];
179
+ for (const agent of agents) {
180
+ expect(resolveAgentName(agent, framework)).toBe(agent);
181
+ }
182
+ });
183
+ it('should maintain delegation behavior for oh-my-opencode with available agents', () => {
184
+ const framework = 'oh-my-opencode';
185
+ const availableAgents = ['sisyphus']; // Provide available agents
186
+ // Delegation should be enabled
187
+ expect(shouldDelegateToSynergy(framework)).toBe(true);
188
+ expect(getDelegationStrategy(framework)).toBe('delegate');
189
+ // Resolution should work when agent is available
190
+ const result = resolveAgentForDelegation('sisyphus', framework, availableAgents);
191
+ expect(result.success).toBe(true);
192
+ expect(result.resolvedAgent).toBe('sisyphus');
193
+ expect(result.shouldFallback).toBe(false);
194
+ });
195
+ });
196
+ describe('Edge Cases and Error Handling', () => {
197
+ it('should handle null agent names gracefully', () => {
198
+ const availableAgents = ['explorer', 'designer'];
199
+ // null agent names should not be available
200
+ expect(isAgentAvailable(null, availableAgents)).toBe(false);
201
+ });
202
+ it('should handle empty agent name strings', () => {
203
+ const availableAgents = ['explorer', 'designer'];
204
+ // Empty string should not match
205
+ expect(isAgentAvailable('', availableAgents)).toBe(false);
206
+ });
207
+ it('should handle case-sensitive agent names', () => {
208
+ const framework = 'oh-my-opencode-slim';
209
+ const availableAgents = ['explorer', 'designer'];
210
+ // Agent names are case-sensitive
211
+ expect(isAgentAvailable('Explorer', availableAgents)).toBe(false);
212
+ expect(isAgentAvailable('EXPLORER', availableAgents)).toBe(false);
213
+ expect(isAgentAvailable('explorer', availableAgents)).toBe(true);
214
+ });
215
+ it('should handle whitespace in agent names', () => {
216
+ const availableAgents = ['explorer', 'designer'];
217
+ // Whitespace should matter
218
+ expect(isAgentAvailable(' explorer', availableAgents)).toBe(false);
219
+ expect(isAgentAvailable('explorer ', availableAgents)).toBe(false);
220
+ expect(isAgentAvailable('explorer', availableAgents)).toBe(true);
221
+ });
222
+ });
223
+ describe('Delegation Reason Messages', () => {
224
+ it('should provide clear reasons for fallback in manual mode', () => {
225
+ const result = resolveAgentForDelegation('explore', 'none', []);
226
+ expect(result.success).toBe(false);
227
+ expect(result.shouldFallback).toBe(true);
228
+ expect(result.reason).toBe('No synergy framework active');
229
+ });
230
+ it('should provide clear reasons for missing agent mappings', () => {
231
+ const result = resolveAgentForDelegation('sisyphus', 'oh-my-opencode-slim', ['explorer']);
232
+ expect(result.success).toBe(false);
233
+ expect(result.shouldFallback).toBe(true);
234
+ expect(result.reason).toContain('not available');
235
+ expect(result.reason).toContain('sisyphus');
236
+ expect(result.reason).toContain('oh-my-opencode-slim');
237
+ });
238
+ it('should provide clear reasons for unavailable agents', () => {
239
+ const result = resolveAgentForDelegation('frontend-ui-ux-engineer', 'oh-my-opencode-slim', ['explorer']);
240
+ expect(result.success).toBe(false);
241
+ expect(result.shouldFallback).toBe(true);
242
+ expect(result.reason).toContain('not available');
243
+ expect(result.reason).toContain('designer');
244
+ });
245
+ });
246
+ });
@@ -0,0 +1,24 @@
1
+ import type { SynergyFramework } from './configDetection.js';
2
+ /**
3
+ * Resolve a requested agent name to the framework-specific agent name
4
+ *
5
+ * @param requestedAgent - The agent name requested by the user
6
+ * @param framework - The active synergy framework
7
+ * @returns The framework-specific agent name, or null if not available
8
+ */
9
+ export declare function resolveAgentName(requestedAgent: string, framework: SynergyFramework): string | null;
10
+ /**
11
+ * Get the complete agent mapping for a framework
12
+ *
13
+ * @param framework - The synergy framework
14
+ * @returns The agent mapping object
15
+ */
16
+ export declare function getAgentMapping(framework: SynergyFramework): Record<string, string | null>;
17
+ /**
18
+ * Check if an agent is available in the list of available agents
19
+ *
20
+ * @param agentName - The agent name to check (can be null)
21
+ * @param availableAgents - List of available agents
22
+ * @returns True if the agent is available
23
+ */
24
+ export declare function isAgentAvailable(agentName: string | null, availableAgents: string[]): boolean;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Mapping from generic agent names to framework-specific agent names
3
+ * for oh-my-opencode-slim
4
+ */
5
+ const SLIM_AGENT_MAPPING = {
6
+ // Generic names → slim-specific names
7
+ 'explore': 'explorer',
8
+ 'frontend-ui-ux-engineer': 'designer',
9
+ 'document-writer': 'librarian',
10
+ 'oracle': 'oracle',
11
+ // Slim native names (pass through)
12
+ 'explorer': 'explorer',
13
+ 'designer': 'designer',
14
+ 'librarian': 'librarian',
15
+ // Agents without slim equivalent
16
+ 'sisyphus': null,
17
+ 'orchestrator': null,
18
+ };
19
+ /**
20
+ * Resolve a requested agent name to the framework-specific agent name
21
+ *
22
+ * @param requestedAgent - The agent name requested by the user
23
+ * @param framework - The active synergy framework
24
+ * @returns The framework-specific agent name, or null if not available
25
+ */
26
+ export function resolveAgentName(requestedAgent, framework) {
27
+ if (framework === 'none') {
28
+ return null;
29
+ }
30
+ if (framework === 'oh-my-opencode-slim') {
31
+ // Use the mapping, return null if not found
32
+ return SLIM_AGENT_MAPPING[requestedAgent] ?? null;
33
+ }
34
+ // For oh-my-opencode, pass through agent names unchanged
35
+ if (framework === 'oh-my-opencode') {
36
+ return requestedAgent;
37
+ }
38
+ return null;
39
+ }
40
+ /**
41
+ * Get the complete agent mapping for a framework
42
+ *
43
+ * @param framework - The synergy framework
44
+ * @returns The agent mapping object
45
+ */
46
+ export function getAgentMapping(framework) {
47
+ if (framework === 'oh-my-opencode-slim') {
48
+ return { ...SLIM_AGENT_MAPPING };
49
+ }
50
+ // For oh-my-opencode and none, return empty mapping
51
+ return {};
52
+ }
53
+ /**
54
+ * Check if an agent is available in the list of available agents
55
+ *
56
+ * @param agentName - The agent name to check (can be null)
57
+ * @param availableAgents - List of available agents
58
+ * @returns True if the agent is available
59
+ */
60
+ export function isAgentAvailable(agentName, availableAgents) {
61
+ if (agentName === null) {
62
+ return false;
63
+ }
64
+ return availableAgents.includes(agentName);
65
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,80 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { resolveAgentName, getAgentMapping, isAgentAvailable } from './agentMapping.js';
3
+ describe('Agent Mapping', () => {
4
+ describe('resolveAgentName', () => {
5
+ it('should map generic agent names to oh-my-opencode-slim agents', () => {
6
+ expect(resolveAgentName('explore', 'oh-my-opencode-slim')).toBe('explorer');
7
+ expect(resolveAgentName('frontend-ui-ux-engineer', 'oh-my-opencode-slim')).toBe('designer');
8
+ expect(resolveAgentName('document-writer', 'oh-my-opencode-slim')).toBe('librarian');
9
+ expect(resolveAgentName('oracle', 'oh-my-opencode-slim')).toBe('oracle');
10
+ });
11
+ it('should pass through oh-my-opencode-slim native agent names', () => {
12
+ expect(resolveAgentName('explorer', 'oh-my-opencode-slim')).toBe('explorer');
13
+ expect(resolveAgentName('designer', 'oh-my-opencode-slim')).toBe('designer');
14
+ expect(resolveAgentName('librarian', 'oh-my-opencode-slim')).toBe('librarian');
15
+ });
16
+ it('should return null for agents without slim equivalent', () => {
17
+ expect(resolveAgentName('sisyphus', 'oh-my-opencode-slim')).toBe(null);
18
+ });
19
+ it('should return null for unknown agent names in slim framework', () => {
20
+ expect(resolveAgentName('unknown-agent', 'oh-my-opencode-slim')).toBe(null);
21
+ });
22
+ it('should pass through agent names for oh-my-opencode framework', () => {
23
+ expect(resolveAgentName('sisyphus', 'oh-my-opencode')).toBe('sisyphus');
24
+ expect(resolveAgentName('oracle', 'oh-my-opencode')).toBe('oracle');
25
+ expect(resolveAgentName('explore', 'oh-my-opencode')).toBe('explore');
26
+ });
27
+ it('should return null when framework is none', () => {
28
+ expect(resolveAgentName('explore', 'none')).toBe(null);
29
+ expect(resolveAgentName('oracle', 'none')).toBe(null);
30
+ });
31
+ });
32
+ describe('getAgentMapping', () => {
33
+ it('should return the complete mapping for oh-my-opencode-slim', () => {
34
+ const mapping = getAgentMapping('oh-my-opencode-slim');
35
+ expect(mapping).toHaveProperty('explore', 'explorer');
36
+ expect(mapping).toHaveProperty('explorer', 'explorer');
37
+ expect(mapping).toHaveProperty('frontend-ui-ux-engineer', 'designer');
38
+ expect(mapping).toHaveProperty('designer', 'designer');
39
+ expect(mapping).toHaveProperty('document-writer', 'librarian');
40
+ expect(mapping).toHaveProperty('librarian', 'librarian');
41
+ expect(mapping).toHaveProperty('oracle', 'oracle');
42
+ expect(mapping).toHaveProperty('sisyphus', null);
43
+ });
44
+ it('should return identity mapping for oh-my-opencode', () => {
45
+ const mapping = getAgentMapping('oh-my-opencode');
46
+ // For oh-my-opencode, agent names pass through unchanged
47
+ expect(mapping).toEqual({});
48
+ });
49
+ it('should return empty mapping when framework is none', () => {
50
+ const mapping = getAgentMapping('none');
51
+ expect(mapping).toEqual({});
52
+ });
53
+ });
54
+ describe('isAgentAvailable', () => {
55
+ it('should return true if agent is in available list', () => {
56
+ const availableAgents = ['explorer', 'designer', 'librarian'];
57
+ expect(isAgentAvailable('explorer', availableAgents)).toBe(true);
58
+ expect(isAgentAvailable('designer', availableAgents)).toBe(true);
59
+ });
60
+ it('should return false if agent is not in available list', () => {
61
+ const availableAgents = ['explorer', 'designer'];
62
+ expect(isAgentAvailable('librarian', availableAgents)).toBe(false);
63
+ expect(isAgentAvailable('oracle', availableAgents)).toBe(false);
64
+ });
65
+ it('should return false for null agent name', () => {
66
+ const availableAgents = ['explorer', 'designer'];
67
+ expect(isAgentAvailable(null, availableAgents)).toBe(false);
68
+ });
69
+ it('should return false when available agents list is empty', () => {
70
+ expect(isAgentAvailable('explorer', [])).toBe(false);
71
+ });
72
+ it('should handle case-sensitive matching', () => {
73
+ const availableAgents = ['explorer', 'Designer'];
74
+ expect(isAgentAvailable('explorer', availableAgents)).toBe(true);
75
+ expect(isAgentAvailable('Explorer', availableAgents)).toBe(false);
76
+ expect(isAgentAvailable('Designer', availableAgents)).toBe(true);
77
+ expect(isAgentAvailable('designer', availableAgents)).toBe(false);
78
+ });
79
+ });
80
+ });
@@ -1,7 +1,10 @@
1
+ export type SynergyFramework = 'none' | 'oh-my-opencode' | 'oh-my-opencode-slim';
1
2
  export interface ConfigDetectionResult {
2
3
  hasCDDInOpenCode: boolean;
3
4
  hasCDDInOMO: boolean;
4
5
  synergyActive: boolean;
5
6
  cddModel?: string;
7
+ synergyFramework: SynergyFramework;
8
+ slimAgents?: string[];
6
9
  }
7
10
  export declare function detectCDDConfig(): ConfigDetectionResult;
@@ -5,15 +5,36 @@ export function detectCDDConfig() {
5
5
  const opencodeConfigDir = join(homedir(), ".config", "opencode");
6
6
  const opencodeJsonPath = join(opencodeConfigDir, "opencode.json");
7
7
  const omoJsonPath = join(opencodeConfigDir, "oh-my-opencode.json");
8
+ const slimJsonPath = join(opencodeConfigDir, "oh-my-opencode-slim.json");
8
9
  let hasCDDInOpenCode = false;
9
10
  let hasCDDInOMO = false;
10
11
  let cddModel;
11
- // Check oh-my-opencode.json first (higher priority)
12
- if (existsSync(omoJsonPath)) {
12
+ let synergyFramework = 'none';
13
+ let slimAgents;
14
+ // Check oh-my-opencode-slim.json first (highest priority for synergy)
15
+ if (existsSync(slimJsonPath)) {
16
+ try {
17
+ const config = JSON.parse(readFileSync(slimJsonPath, "utf-8"));
18
+ // Check if config is not empty and has actual content
19
+ if (config && Object.keys(config).length > 0) {
20
+ synergyFramework = 'oh-my-opencode-slim';
21
+ // Extract available agents (filter out disabled ones)
22
+ const coreAgents = ['explorer', 'librarian', 'oracle', 'designer'];
23
+ const disabledAgents = new Set(config.disabled_agents ?? []);
24
+ slimAgents = coreAgents.filter(agent => !disabledAgents.has(agent));
25
+ }
26
+ }
27
+ catch (e) {
28
+ // Silently fail on parse errors
29
+ }
30
+ }
31
+ // Check oh-my-opencode.json (only if slim is not active)
32
+ if (synergyFramework === 'none' && existsSync(omoJsonPath)) {
13
33
  try {
14
34
  const config = JSON.parse(readFileSync(omoJsonPath, "utf-8"));
15
35
  if (config.agents && config.agents.cdd) {
16
36
  hasCDDInOMO = true;
37
+ synergyFramework = 'oh-my-opencode';
17
38
  // Extract model from oh-my-opencode.json
18
39
  if (config.agents.cdd.model) {
19
40
  cddModel = config.agents.cdd.model;
@@ -43,7 +64,9 @@ export function detectCDDConfig() {
43
64
  return {
44
65
  hasCDDInOpenCode,
45
66
  hasCDDInOMO,
46
- synergyActive: hasCDDInOMO,
67
+ synergyActive: synergyFramework !== 'none',
47
68
  cddModel,
69
+ synergyFramework,
70
+ slimAgents,
48
71
  };
49
72
  }