opencode-conductor-cdd-plugin 1.0.0-beta.15 → 1.0.0-beta.17
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 +43 -4
- package/dist/prompts/agent/cdd.md +11 -7
- package/dist/prompts/cdd/implement.json +1 -1
- package/dist/prompts/cdd/newTrack.json +1 -1
- package/dist/prompts/cdd/revert.json +1 -1
- package/dist/prompts/cdd/setup.json +2 -2
- package/dist/prompts/cdd/status.json +1 -1
- package/dist/test/integration/slim-synergy.test.d.ts +1 -0
- package/dist/test/integration/slim-synergy.test.js +246 -0
- package/dist/utils/agentMapping.d.ts +24 -0
- package/dist/utils/agentMapping.js +65 -0
- package/dist/utils/agentMapping.test.d.ts +1 -0
- package/dist/utils/agentMapping.test.js +80 -0
- package/dist/utils/archive-tracks.d.ts +25 -0
- package/dist/utils/archive-tracks.js +1 -0
- package/dist/utils/configDetection.d.ts +3 -0
- package/dist/utils/configDetection.js +26 -3
- package/dist/utils/configDetection.test.js +87 -0
- package/dist/utils/synergyDelegation.d.ts +33 -0
- package/dist/utils/synergyDelegation.js +65 -0
- package/dist/utils/synergyDelegation.test.d.ts +1 -0
- package/dist/utils/synergyDelegation.test.js +88 -0
- package/dist/utils/synergyState.d.ts +18 -0
- package/dist/utils/synergyState.js +52 -0
- package/dist/utils/synergyState.test.d.ts +1 -0
- package/dist/utils/synergyState.test.js +185 -0
- package/dist/utils/synergyStatus.d.ts +27 -0
- package/dist/utils/synergyStatus.js +54 -0
- package/dist/utils/synergyStatus.test.d.ts +1 -0
- package/dist/utils/synergyStatus.test.js +143 -0
- package/package.json +1 -1
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface TrackMetadata {
|
|
2
|
+
track_id: string;
|
|
3
|
+
type: string;
|
|
4
|
+
status: string;
|
|
5
|
+
created_at: string;
|
|
6
|
+
updated_at: string;
|
|
7
|
+
description: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ArchiveResult {
|
|
10
|
+
archived: string[];
|
|
11
|
+
skipped: string[];
|
|
12
|
+
errors: {
|
|
13
|
+
trackId: string;
|
|
14
|
+
error: string;
|
|
15
|
+
}[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Interface representing the structure of a track's plan file
|
|
19
|
+
*/
|
|
20
|
+
export interface TrackPlan {
|
|
21
|
+
tasks: {
|
|
22
|
+
name: string;
|
|
23
|
+
completed: boolean;
|
|
24
|
+
}[];
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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
|
-
|
|
12
|
-
|
|
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:
|
|
67
|
+
synergyActive: synergyFramework !== 'none',
|
|
47
68
|
cddModel,
|
|
69
|
+
synergyFramework,
|
|
70
|
+
slimAgents,
|
|
48
71
|
};
|
|
49
72
|
}
|
|
@@ -11,6 +11,7 @@ vi.mock("os", () => ({
|
|
|
11
11
|
describe("configDetection", () => {
|
|
12
12
|
const opencodeJsonPath = "/home/user/.config/opencode/opencode.json";
|
|
13
13
|
const omoJsonPath = "/home/user/.config/opencode/oh-my-opencode.json";
|
|
14
|
+
const slimJsonPath = "/home/user/.config/opencode/oh-my-opencode-slim.json";
|
|
14
15
|
beforeEach(() => {
|
|
15
16
|
vi.clearAllMocks();
|
|
16
17
|
});
|
|
@@ -116,4 +117,90 @@ describe("configDetection", () => {
|
|
|
116
117
|
expect(result.hasCDDInOMO).toBe(true);
|
|
117
118
|
expect(result.cddModel).toBeUndefined();
|
|
118
119
|
});
|
|
120
|
+
// New tests for oh-my-opencode-slim detection
|
|
121
|
+
describe("oh-my-opencode-slim detection", () => {
|
|
122
|
+
it("should detect oh-my-opencode-slim when only slim config is present", () => {
|
|
123
|
+
vi.mocked(existsSync).mockImplementation((path) => path === slimJsonPath);
|
|
124
|
+
vi.mocked(readFileSync).mockImplementation((path) => {
|
|
125
|
+
if (path === slimJsonPath) {
|
|
126
|
+
return JSON.stringify({
|
|
127
|
+
agents: { designer: { model: "google/gemini-3-flash" } }
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return "";
|
|
131
|
+
});
|
|
132
|
+
const result = detectCDDConfig();
|
|
133
|
+
expect(result.synergyActive).toBe(true);
|
|
134
|
+
expect(result.synergyFramework).toBe('oh-my-opencode-slim');
|
|
135
|
+
expect(result.slimAgents).toEqual(['explorer', 'librarian', 'oracle', 'designer']);
|
|
136
|
+
});
|
|
137
|
+
it("should prioritize oh-my-opencode-slim over oh-my-opencode when both present", () => {
|
|
138
|
+
vi.mocked(existsSync).mockReturnValue(true);
|
|
139
|
+
vi.mocked(readFileSync).mockImplementation((path) => {
|
|
140
|
+
if (path === slimJsonPath) {
|
|
141
|
+
return JSON.stringify({ agents: {} });
|
|
142
|
+
}
|
|
143
|
+
if (path === omoJsonPath) {
|
|
144
|
+
return JSON.stringify({ agents: { cdd: { model: "model-from-omo" } } });
|
|
145
|
+
}
|
|
146
|
+
return "";
|
|
147
|
+
});
|
|
148
|
+
const result = detectCDDConfig();
|
|
149
|
+
expect(result.synergyFramework).toBe('oh-my-opencode-slim');
|
|
150
|
+
expect(result.synergyActive).toBe(true);
|
|
151
|
+
});
|
|
152
|
+
it("should filter out disabled agents from slim config", () => {
|
|
153
|
+
vi.mocked(existsSync).mockImplementation((path) => path === slimJsonPath);
|
|
154
|
+
vi.mocked(readFileSync).mockImplementation((path) => {
|
|
155
|
+
if (path === slimJsonPath) {
|
|
156
|
+
return JSON.stringify({
|
|
157
|
+
disabled_agents: ['oracle', 'designer']
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return "";
|
|
161
|
+
});
|
|
162
|
+
const result = detectCDDConfig();
|
|
163
|
+
expect(result.synergyFramework).toBe('oh-my-opencode-slim');
|
|
164
|
+
expect(result.slimAgents).toEqual(['explorer', 'librarian']);
|
|
165
|
+
});
|
|
166
|
+
it("should handle empty oh-my-opencode-slim config", () => {
|
|
167
|
+
vi.mocked(existsSync).mockImplementation((path) => path === slimJsonPath);
|
|
168
|
+
vi.mocked(readFileSync).mockImplementation((path) => {
|
|
169
|
+
if (path === slimJsonPath) {
|
|
170
|
+
return JSON.stringify({});
|
|
171
|
+
}
|
|
172
|
+
return "";
|
|
173
|
+
});
|
|
174
|
+
const result = detectCDDConfig();
|
|
175
|
+
expect(result.synergyFramework).toBe('none');
|
|
176
|
+
expect(result.synergyActive).toBe(false);
|
|
177
|
+
});
|
|
178
|
+
it("should fallback to oh-my-opencode when slim config is invalid", () => {
|
|
179
|
+
vi.mocked(existsSync).mockReturnValue(true);
|
|
180
|
+
vi.mocked(readFileSync).mockImplementation((path) => {
|
|
181
|
+
if (path === slimJsonPath) {
|
|
182
|
+
return "invalid json";
|
|
183
|
+
}
|
|
184
|
+
if (path === omoJsonPath) {
|
|
185
|
+
return JSON.stringify({ agents: { cdd: { model: "model-from-omo" } } });
|
|
186
|
+
}
|
|
187
|
+
return "";
|
|
188
|
+
});
|
|
189
|
+
const result = detectCDDConfig();
|
|
190
|
+
expect(result.synergyFramework).toBe('oh-my-opencode');
|
|
191
|
+
expect(result.hasCDDInOMO).toBe(true);
|
|
192
|
+
});
|
|
193
|
+
it("should handle malformed slim config gracefully", () => {
|
|
194
|
+
vi.mocked(existsSync).mockImplementation((path) => path === slimJsonPath);
|
|
195
|
+
vi.mocked(readFileSync).mockImplementation((path) => {
|
|
196
|
+
if (path === slimJsonPath) {
|
|
197
|
+
throw new Error("File read error");
|
|
198
|
+
}
|
|
199
|
+
return "";
|
|
200
|
+
});
|
|
201
|
+
const result = detectCDDConfig();
|
|
202
|
+
expect(result.synergyFramework).toBe('none');
|
|
203
|
+
expect(result.synergyActive).toBe(false);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
119
206
|
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { SynergyFramework } from './configDetection.js';
|
|
2
|
+
/**
|
|
3
|
+
* Result of agent resolution for delegation
|
|
4
|
+
*/
|
|
5
|
+
export interface DelegationResult {
|
|
6
|
+
success: boolean;
|
|
7
|
+
resolvedAgent: string | null;
|
|
8
|
+
shouldFallback: boolean;
|
|
9
|
+
reason?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Determine if delegation to synergy framework should occur
|
|
13
|
+
*
|
|
14
|
+
* @param framework - The active synergy framework
|
|
15
|
+
* @returns True if delegation should occur
|
|
16
|
+
*/
|
|
17
|
+
export declare function shouldDelegateToSynergy(framework: SynergyFramework): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Get the delegation strategy file name based on active framework
|
|
20
|
+
*
|
|
21
|
+
* @param framework - The active synergy framework
|
|
22
|
+
* @returns Strategy file name ('delegate' or 'manual')
|
|
23
|
+
*/
|
|
24
|
+
export declare function getDelegationStrategy(framework: SynergyFramework): 'delegate' | 'manual';
|
|
25
|
+
/**
|
|
26
|
+
* Resolve an agent name for delegation, handling mapping and availability
|
|
27
|
+
*
|
|
28
|
+
* @param requestedAgent - The agent name requested
|
|
29
|
+
* @param framework - The active synergy framework
|
|
30
|
+
* @param availableAgents - List of available agents in the framework
|
|
31
|
+
* @returns Delegation result with resolved agent or fallback info
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveAgentForDelegation(requestedAgent: string, framework: SynergyFramework, availableAgents: string[]): DelegationResult;
|